SCOPE: find the potential important features for credit card selling, and rank the clients with the hightest probabilities

I would like to analyze customer preferences and financial behaviors to understand the potential cross-selling opportunities. Finally, the prediction model can help the banks to identify good clients who may buy credit card and bad clients who are unlikely to pay the credits.

# Load the libraries
library(tidymodels)
library(tidyverse)
library(dplyr)
library(DataExplorer)
library(ggplot2)
library(lubridate)
library(rlang)
library(gridExtra)
library(cowplot)
library(caret)
library("pROC")
library(glmnet)
library(Matrix)
library(ROSE)
library(randomForest)
library(caret)
library(ranger)
Warning: package ‘ranger’ was built under R version 4.2.3
library(DALEX) 
library(DALEXtra)
library(kknn)

1. Data preprocessing

1.1 exploratory data analysis

1.1.1 load relevant dataset and check data quality

account <- read.csv(file = 'data/account.csv',sep = ';')
card <- read.csv(file = 'data/card.csv',sep = ';')
client <- read.csv(file = 'data/client.csv',sep = ';')
disp <- read.csv(file = 'data/disp.csv',sep = ';')
district <- read.csv(file = 'data/district.csv',sep = ';')
loan <- read.csv(file = 'data/loan.csv',sep = ';')
order <- read.csv(file = 'data/order.csv',sep = ';')
trans <- read.csv(file = 'data/trans.csv',sep = ';')

There are eight dataframes, I will observe them one after another to understand them.

1.1.2 understand and transform data

1.1.2.1 disposition: each record relates together a client with an account
disp <- unique(disp)
print(head(disp,3))
# function print_df_info will print some basic information of a dataframe
print_df_info <- function(df) {
  cat(dim(df)[1], "rows and", dim(df)[2], "columns\n")
  cat("Data types:\n")
  print(sapply(df, class))
  cat("number of NAs in columns:\n")
  cat("  ", colSums(is.na(df)), "\n", sep = "  ")
  cat(sum(rowSums(is.na(df)) > 0), "rows contain missing values\n")

  if (length(df) > 0) {
    for (col in names(df)) {
      if (is.numeric(df[[col]])) {
        cat("\nColumn", col, "\n")
        print(summary(df[[col]]))
      } else if (is.character(df[[col]])) {
        cat("\nColumn", col, "unique Values:\n")
        unique_values <- unique(df[[col]])
        cat("  ", paste(unique_values, collapse = ", "), "\n")
      }}} else {cat("No data in this dataframe.\n")}}

print_df_info(disp)
5369 rows and 4 columns
Data types:
    disp_id   client_id  account_id        type 
  "integer"   "integer"   "integer" "character" 
number of NAs in columns:
    0  0  0  0  
0 rows contain missing values

Column disp_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1418    2839    3337    4257   13690 

Column client_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1418    2839    3359    4257   13998 

Column account_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1178    2349    2767    3526   11382 

Column type unique Values:
   OWNER, DISPONENT 

There are no NAs or blank cells. Data types are correct.

# generate a function to check relationship between two variables
check_relationship <- function(dataframe, column1, column2) {
  if (all(table(dataframe[[column1]]) == 1) && all(table(dataframe[[column2]]) == 1)) {
    cat("The relationship between", column1, "and", column2, "is one-to-one.\n")
  } else if (any(table(dataframe[[column1]]) > 1) && all(table(dataframe[[column2]]) == 1)) {
    cat("The relationship between", column1, "and", column2, "is one-to-many.\n")
  } else if (all(table(dataframe[[column1]]) == 1) && any(table(dataframe[[column2]]) > 1)) {
    cat("The relationship between", column1, "and", column2, "is many-to-one.\n")
  } else {
    cat("The relationship between", column1, "and", column2, "is neither one-to-one, one-to-many, nor many-to-one.\n")
  }}

check_relationship(disp, "account_id", "disp_id")
The relationship between account_id and disp_id is one-to-many.
check_relationship(disp, "client_id", "disp_id")
The relationship between client_id and disp_id is one-to-one.
1.1.2.2 card: each record describes a credit card issued to an account
card <- unique(card)
print(head(card,3))
# check the data information and quality
print_df_info(card[,1:3])
892 rows and 3 columns
Data types:
    card_id     disp_id        type 
  "integer"   "integer" "character" 
number of NAs in columns:
    0  0  0  
0 rows contain missing values

Column card_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    1.0   229.8   456.5   480.9   684.2  1247.0 

Column disp_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      9    1387    2938    3512    4460   13660 

Column type unique Values:
   classic, junior, gold 
# remove junior credit card customers, because of the specific goals and strategies of the bank
card <- card %>% filter(type != 'junior') 
# Transform 'issued' to a Date type
card$issued <- as.Date(card$issued, format = '%y%m%d')
# rename columns
colnames(card)[3:4] <- c("card_type","card_issued")
print(head(card,3))
check_relationship(card, "card_id", "disp_id")
The relationship between card_id and disp_id is one-to-one.
1.1.2.3 district: each record describes demographic characteristics of a district
district <- unique(district)
print(head(district,3))
# add column names
colnames(district) <- c("district_id","district_name","region","nr_inhabitants","nr_municipalities_1","nr_municipalities_2","nr_municipalities_3","nr_municipalities_4","nr_cities","ratio_urban_inhabitants","average_salary", "unemploy_rate_95","unemploy_rate_96","nr_enterpreneurs_per_1000_inhabitants","nr_commited_crimes_95", "nr_commited_crimes_96")
print(head(district,3))
print_df_info(district)
77 rows and 16 columns
Data types:
                          district_id                         district_name 
                            "integer"                           "character" 
                               region                        nr_inhabitants 
                          "character"                             "integer" 
                  nr_municipalities_1                   nr_municipalities_2 
                            "integer"                             "integer" 
                  nr_municipalities_3                   nr_municipalities_4 
                            "integer"                             "integer" 
                            nr_cities               ratio_urban_inhabitants 
                            "integer"                             "numeric" 
                       average_salary                      unemploy_rate_95 
                            "integer"                           "character" 
                     unemploy_rate_96 nr_enterpreneurs_per_1000_inhabitants 
                            "numeric"                             "integer" 
                nr_commited_crimes_95                 nr_commited_crimes_96 
                          "character"                             "integer" 
number of NAs in columns:
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  
0 rows contain missing values

Column district_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1      20      39      39      58      77 

Column district_name unique Values:
   Hl.m. Praha, Benesov, Beroun, Kladno, Kolin, Kutna Hora, Melnik, Mlada Boleslav, Nymburk, Praha - vychod, Praha - zapad, Pribram, Rakovnik, Ceske Budejovice, Cesky Krumlov, Jindrichuv Hradec, Pelhrimov, Pisek, Prachatice, Strakonice, Tabor, Domazlice, Cheb, Karlovy Vary, Klatovy, Plzen - mesto, Plzen - jih, Plzen - sever, Rokycany, Sokolov, Tachov, Ceska Lipa, Decin, Chomutov, Jablonec n. Nisou, Liberec, Litomerice, Louny, Most, Teplice, Usti nad Labem, Havlickuv Brod, Hradec Kralove, Chrudim, Jicin, Nachod, Pardubice, Rychnov nad Kneznou, Semily, Svitavy, Trutnov, Usti nad Orlici, Blansko, Brno - mesto, Brno - venkov, Breclav, Hodonin, Jihlava, Kromeriz, Prostejov, Trebic, Uherske Hradiste, Vyskov, Zlin, Znojmo, Zdar nad Sazavou, Bruntal, Frydek - Mistek, Jesenik, Karvina, Novy Jicin, Olomouc, Opava, Ostrava - mesto, Prerov, Sumperk, Vsetin 

Column region unique Values:
   Prague, central Bohemia, south Bohemia, west Bohemia, north Bohemia, east Bohemia, south Moravia, north Moravia 

Column nr_inhabitants 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  42821   85852  108871  133885  139012 1204953 

Column nr_municipalities_1 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00   22.00   49.00   48.62   71.00  151.00 

Column nr_municipalities_2 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00   16.00   25.00   24.32   32.00   70.00 

Column nr_municipalities_3 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   4.000   6.000   6.273   8.000  20.000 

Column nr_municipalities_4 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   1.000   2.000   1.727   2.000   5.000 

Column nr_cities 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00    5.00    6.00    6.26    8.00   11.00 

Column ratio_urban_inhabitants 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  33.90   51.90   59.80   63.04   73.50  100.00 

Column average_salary 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   8110    8512    8814    9032    9317   12541 

Column unemploy_rate_95 unique Values:
   0.29, 1.67, 1.95, 4.64, 3.85, 2.95, 2.26, 1.25, 3.39, 0.56, 0.45, 3.83, 2.77, 1.42, 3.13, 1.12, 2.38, 2.83, 2.65, 1.51, 1.10, 1.79, 1.39, 2.47, 2.64, 0.65, 1.62, 2.82, 3.38, 3.52, 2.80, 5.75, 6.43, 1.02, 3.33, 4.46, 7.08, 7.34, 6.49, 3.32, 2.41, 1.72, 2.79, 2.28, 1.78, 1.89, 4.83, 2.51, 2.52, 2.53, 1.60, 1.88, 4.69, 3.73, 3.24, 3.45, 4.76, 1.29, 3.79, 5.74, 3.51, 5.77, 4.09, ?, 6.63, 5.93, 3.80, 4.75, 5.38, 4.73, 4.01 

Column unemploy_rate_96 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.430   2.310   3.600   3.787   4.790   9.400 

Column nr_enterpreneurs_per_1000_inhabitants 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   81.0   105.0   113.0   116.1   126.0   167.0 

Column nr_commited_crimes_95 unique Values:
   85677, 2159, 2824, 5244, 2616, 2640, 4289, 5179, 2987, 3810, 3475, 3804, 1597, 6604, 1845, 1874, 1003, 1740, 999, 1563, 2299, 1089, 2879, 5198, 1822, 6041, 1029, 1580, 818, 2985, 1328, 4340, 4650, 5323, 3384, 5796, 4147, 2653, 4947, 6949, 6445, 1658, 4085, 2166, 2080, 2854, 6079, 1655, 1660, 2123, 3496, 2564, 1850, 18721, 3659, 3729, 2212, 2595, 1879, 2112, 2719, 1562, 4484, 2157, 2247, 3244, 5623, ?, 9878, 4980, 9672, 4355, 18782, 4063, 3736, 3460 

Column nr_commited_crimes_96 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    888    2122    3040    5031    4595   99107 

The data types of columns unemploy_rate_95 and nr_commited_crimes_95 were incorrect. Convert them from categorical to numeric type. The two columns contain also ‘?’ character, they should be replaced with NA

# replace "?" with NA
district$unemploy_rate_95[district$unemploy_rate_95 == "?"] <- NA
district$nr_commited_crimes_95[district$nr_commited_crimes_95 == "?"] <- NA
# transfomr data type to numeric
district$unemploy_rate_95 <- as.numeric(district$unemploy_rate_95)
district$nr_commited_crimes_95 <- as.numeric(district$nr_commited_crimes_95)

Now, I have to deal with the NAs in these two columns. According to my observation, using the ratio between unemploy_rate_95 and unemploy_rate_96 may be a good idea for NA imputation in column unemploy_rate_95. Use same strategy for the nr_commited_crimes_95.

First, calculate the ratios and check how they distributed.

unemploy_ratio <- district$unemploy_rate_95 / district$unemploy_rate_96
crimes_ratio <- district$nr_commited_crimes_95 / district$nr_commited_crimes_96

p_unemploy <- ggplot(data.frame(unemploy_ratio = unemploy_ratio), aes(x = unemploy_ratio)) +
  geom_histogram(bins = 10, fill = "skyblue") +
  labs(
    main = "",
    x = "Ratio (unemploy_rate_95 / unemploy_rate_96)",
    y = "Frequency") + theme_minimal()
p_crime <- ggplot(data.frame(crimes_ratio = crimes_ratio), aes(x = crimes_ratio)) +
  geom_histogram(bins = 10, fill = "skyblue") +
  labs(
    main = "",
    x = "Ratio (nr_commited_crimes_95 / nr_commited_crimes_96)",
    y = "Frequency") + theme_minimal()
grid.arrange(p_unemploy, p_crime, ncol=1)

Ratio of unemploy_rate: It is slightly left skewed distributed. However, the distribution is from only 77 values. The ratio values mostly are between 0.7 and 0.9. I will use the median value of the ratio to impute the NAs in unemploy_rate_95 column.

Ratio of commited_crimes: It is light right-skewed distributed, with values mostly between 0.9 and 1.1.

I will use the median values of ratio to impute NAs.

# Impute NAs in unemploy_rate_95
district$unemploy_rate_95[is.na(district$unemploy_rate_95)] <- district$unemploy_rate_96[is.na(district$unemploy_rate_95)] * median(unemploy_ratio, na.rm = TRUE)
# Impute NAs in nr_commited_crimes_95
district$nr_commited_crimes_95[is.na(district$nr_commited_crimes_95)] <- district$nr_commited_crimes_96[is.na(district$nr_commited_crimes_95)] * median(crimes_ratio, na.rm = TRUE)
cat("Number of NAs in each column:",colSums(is.na(district)))
Number of NAs in each column: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1.1.2.4 client:each record describes characteristics of a client
client <- unique(client)
print(head(client,3))
# check data quality
print_df_info(client)
5369 rows and 3 columns
Data types:
   client_id birth_number  district_id 
   "integer"    "integer"    "integer" 
number of NAs in columns:
    0  0  0  
0 rows contain missing values

Column client_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1418    2839    3359    4257   13998 

Column birth_number 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 110820  406009  540829  535115  681013  875927 

Column district_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00   14.00   38.00   37.31   60.00   77.00 
  • the birth_number column in dataframe client represents for client’s birthday. this should be transformed to date type
  • the birth_number with YYMM+50DD is for woman, so here could add a new column ‘gender’
client <- client %>%
  mutate(
    mid_two_digits = as.numeric(substr(birth_number, 3, 4)),
    gender = ifelse(mid_two_digits > 50, "female", "male"), # add gender
    birth_number_ = ifelse(mid_two_digits > 50, birth_number - 5000, birth_number), 
    birthday = as.Date(paste0("19", birth_number_), format = "%Y%m%d")) # add birthday as date type
# check if the wangling is correct
print(head(client, n = 5))
# it seems correct, now drop the irrelavant columns
client <- client %>% select(-c(mid_two_digits,birth_number,birth_number_))
head(client, n = 3)
check_relationship(client, "client_id", "district_id")
The relationship between client_id and district_id is many-to-one.
1.1.2.5 account: each record describes static characteristics of an account
account <- unique(account)
account[account == ""] <- NA
print(head(account,3))
# inspect data quality
print_df_info(account)
4500 rows and 4 columns
Data types:
 account_id district_id   frequency        date 
  "integer"   "integer" "character"   "integer" 
number of NAs in columns:
    0  0  0  0  
0 rows contain missing values

Column account_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1183    2368    2786    3552   11382 

Column district_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00   13.00   38.00   37.31   60.00   77.00 

Column frequency unique Values:
   POPLATEK MESICNE, POPLATEK PO OBRATU, POPLATEK TYDNE 

Column date 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 930101  931227  960102  951655  961101  971229 
  • ‘date’ column should be transformed to date type
# convert the 'date' column to Date format, rename
account$account_date <- ymd(account$date + 19000000)
account <- subset(account, select = -date)
colnames(account)[3] <- "account_freq"
print(head(account,3))
check_relationship(account, "account_id", "district_id")
The relationship between account_id and district_id is many-to-one.
1.1.2.6 loan: each record describes a loan granted for a given account
loan <- unique(loan)
print(head(loan,3))
# inspect data quality
print_df_info(loan)
682 rows and 7 columns
Data types:
    loan_id  account_id        date      amount    duration    payments      status 
  "integer"   "integer"   "integer"   "integer"   "integer"   "numeric" "character" 
number of NAs in columns:
    0  0  0  0  0  0  0  
0 rows contain missing values

Column loan_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   4959    5578    6176    6172    6752    7308 

Column account_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      2    2967    5738    5824    8686   11362 

Column date 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 930705  950704  970206  963028  971212  981208 

Column amount 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   4980   66732  116928  151410  210654  590820 

Column duration 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  12.00   24.00   36.00   36.49   48.00   60.00 

Column payments 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    304    2477    3934    4191    5814    9910 

Column status unique Values:
   B, A, C, D 

‘date’ column should be transformed to date type

# convert the 'date' column to Date format, rename
loan$loan_date <- ymd(loan$date + 19000000)
loan <- subset(loan, select = -date)
colnames(loan)[3:6] <- c("loan_amount", "loan_duration", "loan_payments","loan_status")
print(head(loan,3))
check_relationship(loan, "loan_id", "account_id")
The relationship between loan_id and account_id is one-to-one.
1.1.2.7 order: each record describes characteristics of a payment order
# drop duplicates, replace blank cells with NA
order <- unique(order)
order[order == ""] <- NA
print(head(order,3))
# inspect data quality
print_df_info(order)
6471 rows and 6 columns
Data types:
   order_id  account_id     bank_to  account_to      amount    k_symbol 
  "integer"   "integer" "character"   "integer"   "numeric" "character" 
number of NAs in columns:
    0  0  0  0  0  0  
0 rows contain missing values

Column order_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  29401   31188   32988   33778   34786   46338 

Column account_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1223    2433    2962    3646   11362 

Column bank_to unique Values:
   YZ, ST, QR, WX, CD, AB, UV, GH, IJ, KL, EF, MN, OP 

Column account_to 
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
     399 24159184 49756062 49399037 74000448 99994199 

Column amount 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1242    2596    3281    4614   14882 

Column k_symbol unique Values:
   SIPO, UVER,  , POJISTNE, LEASING 

There are blank cells in column k_symbol, should be replaced with NA.

In this data, k_symbol and amount are two important features. k_symbol stands for the payment types: - “POJISTNE” stands for insurrance payment - “SIPO” stands for household - “LEASING” stands for leasing - “UVER” stands for loan payment

So I will drop the columns which are probably irrelevant to the project: order_id, bank_to, account_to.

order$k_symbol[order$k_symbol == " "] <- NA
order <- order %>% select(-c(order_id, bank_to, account_to))
check_relationship(order, "account_id", "order_id")
The relationship between account_id and order_id is one-to-many.
print(head(order,3))

convert to a wide table, so that there is only one observation per account_id

1.1.2.8 trans: each record describes one transaction on an account
# drop duplicates
trans <- unique(trans)
print(head(trans,3))
# inspect data quality
print_df_info(trans)
1056320 rows and 10 columns
Data types:
   trans_id  account_id        date        type   operation      amount     balance    k_symbol        bank 
  "integer"   "integer"   "integer" "character" "character"   "numeric"   "numeric" "character" "character" 
    account 
  "integer" 
number of NAs in columns:
    0  0  0  0  0  0  0  0  0  760931  
760931 rows contain missing values

Column trans_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1  430263  858506 1335311 2060979 3682987 

Column account_id 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1    1204    2434    2937    3660   11382 

Column date 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 930101  960116  970410  965675  980228  981231 

Column type unique Values:
   PRIJEM, VYDAJ, VYBER 

Column operation unique Values:
   VKLAD, PREVOD Z UCTU, VYBER, , PREVOD NA UCET, VYBER KARTOU 

Column amount 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    0.0   135.9  2100.0  5924.1  6800.0 87400.0 

Column balance 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 -41126   22402   33143   38518   49604  209637 

Column k_symbol unique Values:
   , DUCHOD, UROK, SIPO, SLUZBY,  , POJISTNE, SANKC. UROK, UVER 

Column bank unique Values:
   , YZ, IJ, ST, UV, MN, OP, AB, CD, WX, GH, EF, QR, KL 

Column account 
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max.     NA's 
       0 17828584 45750951 45670919 72013407 99994199   760931 

Following problems need to be solved: - Datatype of ‘date’ need to changed from integer to date type. - There are many NAs. - ‘type’ column should be either ‘PRIJEM’ or ‘VYDAJ’ values based on the reference. How should deal with ‘VYBER’. - ‘operation’, ‘k_symbol’, ‘bank’, ‘account’ columns have blank cells.

# 'date' column should be transformed to date type.
trans$trans_date <- ymd(trans$date + 19000000)
trans <- subset(trans, select = -date)

# replace "" and " " with NA
trans[trans == ""] <- NA
trans$k_symbol[trans$k_symbol == " "] <- NA
print(head(trans,3))
na_counts <- colSums(is.na(trans))
na_proportions <- na_counts / nrow(trans)

barplot(na_proportions, names.arg = names(na_proportions), col = "skyblue", 
        main = "Proportion of NA Values by Column", ylab = "Proportion")

There are NAs in columns operation (around 19% NAs), k_symbol (around 50% NAs), bank (around 78% NAs), account (around 76% NAs). Now let’s understand the meaning of the columns and how to deal with the NA data (impute NA or drop them).

‘opertion’ column: stores the mode of transaction. “VYBER KARTOU” credit card withdrawal; “VKLAD” credit in cash; “PREVOD Z UCTU” collection from another bank; “VYBER” withdrawal in cash; “PREVOD NA UCET” remittance to another bank.

‘k_symbol’ column: characterization of the transaction. “POJISTNE” stands for insurrance payment; “SLUZBY” stands for payment for statement; “UROK” stands for interest credited; “SANKC. UROK” sanction interest if negative balance; “SIPO” stands for household; “DUCHOD” stands for old-age pension; “UVER” stands for loan payment.

‘bank’ column: bank of the partner.

‘account’ column: account of the partner.

It seems, the content of k_symbo, bank, account are dependent on the operation type. check how they are related.

# count the NA by operation type
trans %>% group_by(operation) %>%
  summarise(
    Total_Count = n(),
    NA_count_k = sum(is.na(k_symbol)),
    NA_count_bank = sum(is.na(bank)),
    NA_count_account = sum(is.na(account))) %>% print()

From the upper table, we could see that: - though there are 183114 NAs in column operation. But in these 183114 rows, the k_symbol column values are all available , bank and account columns are all NAs. - VYBER KARTOU operation has only ‘account’ information available. - PREVOD Z UCTU and PREVOD NA UCET operations have almost no NAs in bank and account columns. - YKLAD operation has no available k_symbol, bank, account information.

My understanding is: - The availability of columns ‘bank’ and ‘account’ is largely dependent on ‘operation’ type. This indicates that these two columns may not provide meaningful information across all ‘operation’ types. - In addition, over 75% of these two columns are not available. This indicates that imputing the missing values might not provide accurate results. - So I will drop the columns ‘bank’ and ‘account’.

trans <- subset(trans, select = -c(bank, account))

How should I deal with column k_symbol? First check the relationship between operation and k_symbol.

The columns ‘operation’ and ‘k_symbol’ have 14 unique combinations. All combinations seem reasonable and with large count (except VYBER-POJISTNE). However, there is not enough information to impute the NAs in the two columns. So I will bind the two columns as one variable (trans_detail) to solve the NA issue

trans <- trans %>% mutate(operation = ifelse(is.na(operation), ' ', operation),  # replace NAs as blank before binding
         k_symbol = ifelse(is.na(k_symbol), ' ', k_symbol),
         trans_detail = paste(operation, k_symbol, sep = "-")) %>%
  select(-operation, -k_symbol) 
print(head(trans,3))

Now I would check the data which were incorrectly labeled as ‘VYBER’.

All rows with ‘type’ of ‘VYBER’ have ‘trans_detail’ of ‘VYBER-’ (withdrawal in cash). All other rows which are labeled as ‘trans_detail’ of ‘VYBER-’ have the ‘type’ of ‘VYDAJ’ (withdrawal), which makes totally sense. I will replace all the incorrectly labeled ‘VYBER’ in column ‘type’ with ‘VYDAJ’.

trans <- trans %>%
  mutate(type = ifelse(type == 'VYBER', 'VYDAJ', type))
print(head(trans,3))

The amount column in the transaction data is absolute transaction values, this means, deposits (positive) and withdrawals (negative) transactions will all be shown as positive. Absolute transaction amount reflects the total financial flow, and could be useful for assessing an client’s overall financial activity. However, it can not capture the transaction direction. So additionally, I will also introduce the real amount variable (including ‘+/-’) to understand savings or spending patterns of clients. The identification is based on the balance (increased/decreased after adding/minusing a transaction):

trans <- trans %>%
  group_by(account_id) %>% arrange(account_id, trans_date) %>%
  mutate(difference = balance - lag(balance, default = first(balance)),
    `amount(+/-)` = ifelse(difference > 0, amount, amount * -1)) %>% select(-difference)
print(head(trans,3))
check_relationship(trans, "account_id", "trans_id")
The relationship between account_id and trans_id is one-to-many.

1.2 Join all dataframes to combine customers information and banking services.

Before joining all dataframes, I would like to summarize some important relationships. - account_id and district_id: many-to-one - account_id and client_id: one-to-many - account_id and trans_id: one-to-many - account_id and order_id: one-to-many - account_id and loan_id: one-to-one - account_id and disp_id: one-to-many - client_id and disp_id: one-to-one - client_id and district_id: many-to-one - card_id and disp_id is one-to-one

# First join the dataframes disposition, card, client, district, account, order.
df1 <- disp %>% left_join(card, by = "disp_id", suffix = c("_disp","_card")) %>% select(-c(card_id,disp_id)) %>% 
  left_join(client,by = "client_id",suffix = c("_card","_client"))  %>% left_join(district, by = "district_id") %>% select(-district_id) %>% left_join(account, by = "account_id") %>% select(-district_id) %>% left_join(order, by = "account_id") %>% left_join(loan, by = 'account_id', suffix = c("_account","_loan"))
print(head(df1,3))

It worth noting that only owner can issue permanent orders and ask for a loan. This means, the loan dataframe should be joined only to ‘OWNER’. And I will drop the ‘DISPONENT’ type client, only keep the ‘OWNER’ type client. To keep the information ‘how many clients does an account have”, I will generate a new column ’n_clients’ to represent it.

df1 <- df1 %>% group_by(account_id) %>% mutate(n_clients = n())

barplot(table(df1$n_clients), 
        names.arg = names(table(df1$n_clients)), 
        xlab = "Number of Clients per Account", ylab = "Count", main = "Distribution: Number of Clients per Account ")

The plot shows that, around 3700 account_id has only one client (OWNER), around 1800 account has two client (1 OWNER and 1 DISPONENT).

# check if it is true that DISPONENT clients have no credit cards.
df1 %>% group_by(type) %>% summarize(count_card = sum(!is.na(card_type))) %>% print()

The result comfirmed my assumption: DISPONENT clients have no credit cards. Only OWNER clients could be issued credit cards. So I will drop all ‘DISPONENT’ clients rows. Afterwards the column type_disp will only contain one type ‘OWNER’, this means it is not anymore helpful for further analysis. So I will also drop the whole column type_disp.

df1 <- subset(df1, type != "DISPONENT") %>% select(-type,client_id,loan_id)

Age is often an important factor in financial capacity analysis. I would add two variables ‘card_age’ and ‘loan_age’ to represent the client age when the card was issued, and the loan was issued.

df1$card_age <- as.numeric(difftime(df1$card_issued, df1$birthday, units = "days") / 365)
df1$loan_age <- as.numeric(difftime(df1$loan_date, df1$birthday, units = "days") / 365)
print(head(df1,3))

1.3 Identification of existing credit card buyers

This includes determination of purchase date and rollup window, defined by 1 month lag and 12 months history before credit card purchase.

Identify the existing credit card buyers:

buyers <- subset(df1, !is.na(card_type)) %>% select(c(account_id,card_type,card_issued))
nonbuyers<- subset(df1, is.na(card_type)) %>% select(c(account_id,card_type,card_issued))
cat("Number of Buyers:", nrow(buyers), "\n")
Number of Buyers: 747 
cat("Number of Non-Buyers:", nrow(nonbuyers), "\n")
Number of Non-Buyers: 3753 
cat("Ratio of Non-Buyers to Buyers:", round(nrow(nonbuyers)/nrow(buyers),1), "\n")
Ratio of Non-Buyers to Buyers: 5 
# Create a vector of values
barplot(c(nrow(buyers), nrow(nonbuyers)), names.arg = c("Credit Card Buyers", "Credit Card Non-Buyers"), ylab = "Count")

ggplot(buyers, aes(x = card_issued)) +
  geom_density(fill = "blue", alpha = 0.7) +  # Density plot
  labs(x = "Issued Date", y = "Density", title = "Distribution of Credit Card Issued Date") +
  theme_minimal()

There is a stable increase of credit cards purchase between 1994 and 1996. since 1996, the growth was rapid and reached the peak density between 1998 and 1999, indicating the period of highest credit card issuance activity.

In the whole dataset, 747 clients have already bought the credit cards, 3753 clients have not purchased credit cards. The ratio of 5 means there are approximately 5 times “nonbuyers” as “buyers” in the ‘card_type’ column. Dataset is severe imbalanced. For the further machine learning modelling, sampling methods (e.g. oversampling, undersampling …) or suitable evaluation metrics for imbalanced data (e.g. f1 score) must be considered.

Warning: Each row in `x` is expected to match at most 1 row in `y`.

We could see that a slow growth of the average monthly transaction in the first 11 months. In the last month, it increased rapidly from around 58000 to 117000. To further understand if the tendency is affected by outliers and skewed distribution, I will also check the median, upper- and lower-quartiles tendency.

# Define a function to calculate and plot statistics 
calculate_and_plot_stats <- function(df,col,y_label,title,a,b) {
  # Calculate mean, median, upper quartile, and lower quartile
  stats <- df %>%
    group_by(months_difference) %>%
    summarize(
      mean_amount = mean(!!sym(col), na.rm = TRUE),
      median_amount = median(!!sym(col), na.rm = TRUE),
      upper_quartile_amount = quantile(!!sym(col), probs = 0.75, na.rm = TRUE),
      lower_quartile_amount = quantile(!!sym(col), probs = 0.25, na.rm = TRUE))
  
  # Create a data frame for labels
  label_df <- data.frame(
    months_difference = -4,
    label = c("Mean","Median", "Upper Quartile", "Lower Quartile"),
    y = c(
      stats$mean_amount[stats$months_difference == -2][1],
      stats$median_amount[stats$months_difference == -2][1],
      stats$upper_quartile_amount[stats$months_difference == -2][1],
      stats$lower_quartile_amount[stats$months_difference == -2][1]))
  
  # Create a line plot
  ggplot(stats, aes(x = months_difference)) +
    geom_line(aes(y = mean_amount), color = "black", linetype = "solid") +
    geom_line(aes(y = median_amount), color = "blue", linetype = "solid") +
    geom_line(aes(y = upper_quartile_amount), color = "red", linetype = "solid") +
    geom_line(aes(y = lower_quartile_amount), color = "green", linetype = "solid") +
    scale_x_continuous(breaks = -12:-1) +
    labs(
      x = "Month",
      y = y_label,
      title = title) +
    geom_text(data = label_df, aes(label = label, x = months_difference, y = y), size = 3.5, vjust = -0.5, hjust = 0.5, color = c("black","blue", "red", "green")) +
    theme_minimal() +
    scale_y_continuous(limits = c(a, b)) + 
    theme(
    axis.title = element_text(size = 10),   # Adjust the axis label text size
    plot.title = element_text(size = 12, hjust = 0.5))}


plot1 = calculate_and_plot_stats(buyers_trans_hist, "roll_monthly_trans", "Monthly Transaction", "Credit Card Buyers",8000,85000)
plot1

The median, upper quartile, lower quartile lines show very similar tendency as the average transaction amount. But the median line is consistently lower than the average line. This means the data is left (negative) skewed distributed. In this case, it is more reasonable to use median to measure the tendency.

1.4 Determination of non-buyers for comparison (incl. roll-up window).

plot2 = calculate_and_plot_stats(nonbuyers_trans_hist, "roll_monthly_trans", " ", " Credit Card Non-Buyers",8000,85000)
common_title <- ggdraw() + draw_label("12 Months Rollup Transaction History", fontface='bold', size = 14)
bottom_row <- plot_grid(plot1, plot2, ncol = 2)
plot_grid(common_title, bottom_row, nrow = 2, rel_heights = c(0.2, 1))

Analysis of non-buyers: All four statistics lines have very similar tendency, where median is constantly lower than mean, this indicates the data is negative (left) skewed distributed. In such case, median is more robust than mean to represent the data. non-buyers transaction tendency of the last 12 months is relatively stable with a peak at seventh month. The amount of 12th month and last month are obviously higher than the other months.

Summary based on the comparison of transaction rollup windows Buyers VS non-Buyers: - the monthly transaction amount of buyers (median: around 40,000 - 95,000) is much larger than the nonbuyers (median: around 18,000 - 29,000). - buyers and non-buyers have differently tendency over the last 12 months before credit card purchase. This means, transaction amount related features may be important to classify credit card buyers and non-buyers. For machine learning modeling, I will include comprehensive client-specific statistical feature engineering for the transaction amount. - both dataset are imbalanced, balancing methods or evaluation metrics for imbalanced data must be considered.

1.5 Constructing the assets and turnovers in the rollup window based on the transaction history.

Based on the data we have, asset could be calculated like this: asset = balance - loan (if the account has loan). Of course, I should consider how much loan has already been paid for the time point of asset calculation. The “UVER” k_symbol in transaction stands for loan payment. With this we could calculate the remaining loan for each observations in transaction data.

trans_loan <- trans %>% left_join(loan, by = "account_id", suffix = c("_trans","_loan")) 
colnames(trans_loan)[4] <- "trans_amount"

# split the transaction loan data into two parts
# part 1: the account had loan from the bank; here asset = balance - remaining loan
trans_loan_1 <- trans_loan %>%
  filter(trans_date >= loan_date) %>%
  arrange(account_id, trans_date) %>%
  group_by(account_id) %>% mutate(
    paid_loan = ifelse(grepl("UVER", trans_detail, ignore.case = TRUE), trans_amount, 0), # amount of paid loan in each transaction
    accum_paid_loan = cumsum(paid_loan), # accumulated paid loan
    remaining_loan = loan_amount - accum_paid_loan, # remaining loan
    remaining_loan = ifelse(remaining_loan < 0, 0, remaining_loan), # replace negative remaining loan as 0, this means loan is totally paid.
    asset = balance - remaining_loan) %>% ungroup() %>% select(-c(loan_id,paid_loan,accum_paid_loan,remaining_loan)) # asset 
# part 2: the account which has never been issued a loan, and the account before loan is issued.
trans_loan_2 <- trans_loan %>% filter(trans_date < loan_date | is.na(loan_date)) %>% mutate(asset = balance) %>% select(-loan_id)
trans_loan <- rbind(trans_loan_1, trans_loan_2) 
print(head(trans_loan,5))

Now, I will join the asset data to the transaction data of buyers and non-buyers

buyers_trans <- buyers_trans %>% left_join(trans_loan[, c("trans_id", "asset")], by = 'trans_id')
nonbuyers_trans <- nonbuyers_trans %>% left_join(trans_loan[, c("trans_id", "asset")], by = 'trans_id')
print(head(buyers_trans,5))

construct the assets using 12-month rollup window After each transaction, the asset of an accout get updated, this means each account contains many asset records. In this case it makes more sense to use average asset within a month to represent the monthly asset.

plot3 = calculate_and_plot_stats(buyers_asset_hist, "roll_monthly_asset", "Monthly Asset", "Credit Card Buyers",2000,12000)
plot4 = calculate_and_plot_stats(nonbuyers_asset_hist, "roll_monthly_asset", " ", "Credit Card Non-Buyers",2000,12000)

common_title <- ggdraw() + draw_label("12 Months Rollup Asset History", fontface='bold', size = 14)
bottom_row <- plot_grid(plot3, plot4, ncol = 2)
plot_grid(common_title, bottom_row, nrow = 2, rel_heights = c(0.2, 1))

Compare Asset rollup history of buyers and non-buyers: - The buyers have significant higher asset (median: around 8300) than the nonbuyers (median: 4900 - 5500) - The asset of buyers is slightly increased over the 12 months, in contrast, the nonbuyers’ asset amount is decreasing overtime. - This means, the asset (slope, mean, median, quartiles) could be an important feature for identifying buyers and nonbuyers.

1.6 Deriving customer-specific, statistical key figures for assets and sales in the rollup window using functions.

statistical feature engineering

Warning: There were 11 warnings in `summarize()`.
The first warning was:
ℹ In argument: `robust_trend_trans = coef(rlm(roll_monthly_trans ~ seq_along(roll_monthly_trans)))[2]`.
ℹ In group 15: `account_id = 108`.
Caused by warning in `rlm.default()`:
! 'rlm' failed to converge in 20 steps
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 10 remaining warnings.Warning: There were 59 warnings in `summarize()`.
The first warning was:
ℹ In argument: `robust_trend_trans = coef(rlm(roll_monthly_trans ~ seq_along(roll_monthly_trans)))[2]`.
ℹ In group 58: `account_id = 69`.
Caused by warning in `rlm.default()`:
! 'rlm' failed to converge in 20 steps
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 58 remaining warnings.Warning: There were 25 warnings in `summarize()`.
The first warning was:
ℹ In argument: `robust_trend_asset = coef(rlm(roll_monthly_asset ~ seq_along(roll_monthly_asset)))[2]`.
ℹ In group 6: `account_id = 65`.
Caused by warning in `rlm.default()`:
! 'rlm' failed to converge in 20 steps
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 24 remaining warnings.Warning: There were 61 warnings in `summarize()`.
The first warning was:
ℹ In argument: `robust_trend_asset = coef(rlm(roll_monthly_asset ~ seq_along(roll_monthly_asset)))[2]`.
ℹ In group 36: `account_id = 41`.
Caused by warning in `rlm.default()`:
! 'rlm' failed to converge in 20 steps
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 60 remaining warnings.
Warning: ‘MASS’ namespace cannot be unloaded:
  namespace ‘MASS’ is imported by ‘ipred’ so cannot be unloaded
# join the statistical features to the other data by account_id
df <- df1 %>% left_join(trans_stats, by = 'account_id') %>% left_join(asset_stats, by = 'account_id')
print(head(df,3))
# check again if one account_id has only one data point.
df_count <- df %>% group_by(account_id) %>% summarize(row_count = n())
if (any(df_count$row_count > 1)) {print("Some account_id values have more than one row.")} else {print("Each account_id has only one row.")}
[1] "Each account_id has only one row."
cat("Number of Rows:", dim(df)[1], "\n")
Number of Rows: 4500 
cat("Number of Columns:", dim(df)[2], "\n")
Number of Columns: 70 
cat("Total Number of NAs in the DataFrame:", sum(colSums(is.na(df))), "\n")
Total Number of NAs in the DataFrame: 42437 

1.7 Cleaning up unnecessary information and checking the structure of the modeling data

  • Loan informations are only available for the accounts which have been issued loan. For the accounts without loan, NAs in these varialbes can not be filled. So I will generate a new column ‘loan_issuance’ to represent if an account has been issued loan, afterwards drop the other irrelavant loan variables.
  • Similarly, card relevant information is only available for card buyers, so I will also drop columns card_issued, card_age. The content of card_type should be changed to buyers (classic, gold) and nonbuyers (NA).
  • drop account_id or client_id, because client_id and account_id are one-to-one related, client_id is important for identifying the top-N important clients in later part. So I will drop account_id.
  • add the ages of clients in 1999-1-1 and ages of accounts (as the task was established), afterwards drop birthday, account_date.
df <- df %>%
  mutate(
    loan_issuance = ifelse(is.na(loan_id), "No", "Yes"),
    card_type = ifelse(is.na(card_type), "nonbuyers", "buyers"),
    client_age = as.numeric(difftime('1999-01-01', birthday, units = "days")) / 365.0,
    account_age = as.numeric(difftime('1999-01-01', account_date, units = "days")) / 365.0
  ) %>%
  dplyr::select(-c(account_id, loan_id, loan_amount, loan_duration, loan_payments, loan_status, loan_date, loan_age, card_issued, card_age, birthday, account_date))
Adding missing grouping variables: `account_id`
df <- df[, 2:ncol(df)]

# Round 'client_age' and 'account_age' to 1 decimal place
df$client_age <- round(df$client_age, 1)
df$account_age <- round(df$account_age, 1)

# Count NAs of incomplete columns
col_with_na <- df[, colSums(is.na(df)) != 0]
colSums(is.na(col_with_na))
    order_SIPO     order_UVER    order_OTHER order_POJISTNE  order_LEASING    order_TOTAL 
           742            742            742            742            742            742 

These 6 columns have numeric data. They represent the order amount of accounts. NAs indicate the order amount = 0. So here I will replace the NAs with 0

df[is.na(df)] <- 0
cat( "There are", sum(is.na(df)), "NAs in data df.\n")
There are 0 NAs in data df.
cat("Number of Rows:", dim(df)[1], "\n")
Number of Rows: 4500 
cat("Number of Columns:", dim(df)[2], "\n","Data size is moderate.")
Number of Columns: 61 
 Data size is moderate.
print(head(df))
colnames(df)
 [1] "client_id"                             "card_type"                            
 [3] "gender"                                "district_name"                        
 [5] "region"                                "nr_inhabitants"                       
 [7] "nr_municipalities_1"                   "nr_municipalities_2"                  
 [9] "nr_municipalities_3"                   "nr_municipalities_4"                  
[11] "nr_cities"                             "ratio_urban_inhabitants"              
[13] "average_salary"                        "unemploy_rate_95"                     
[15] "unemploy_rate_96"                      "nr_enterpreneurs_per_1000_inhabitants"
[17] "nr_commited_crimes_95"                 "nr_commited_crimes_96"                
[19] "account_freq"                          "order_SIPO"                           
[21] "order_UVER"                            "order_OTHER"                          
[23] "order_POJISTNE"                        "order_LEASING"                        
[25] "order_TOTAL"                           "n_clients"                            
[27] "mean_trans"                            "median_trans"                         
[29] "upper_quartile_trans"                  "lower_quartile_trans"                 
[31] "min_trans"                             "max_trans"                            
[33] "var_trans"                             "std_trans"                            
[35] "n_pos_changes_trans"                   "n_neg_changes_trans"                  
[37] "mad_trans"                             "sad_trans"                            
[39] "n_above_mean_trans"                    "n_below_mean_trans"                   
[41] "slope_trans"                           "robust_trend_trans"                   
[43] "mean_asset"                            "median_asset"                         
[45] "upper_quartile_asset"                  "lower_quartile_asset"                 
[47] "min_asset"                             "max_asset"                            
[49] "var_asset"                             "std_asset"                            
[51] "n_pos_changes_asset"                   "n_neg_changes_asset"                  
[53] "mad_asset"                             "sad_asset"                            
[55] "n_above_mean_asset"                    "n_below_mean_asset"                   
[57] "slope_asset"                           "robust_trend_asset"                   
[59] "loan_issuance"                         "client_age"                           
[61] "account_age"                          

In summary, available variables could be categoried as following: - Information of account owner: gender, birthday, age, residential district - Info of account: start date, account age, number of clients under the account_id - detailed information of residential district - permanant order amount by order types - transaction: 12-month-rollup statistical variables - asset: 12-month-rollup statistical variables

1.8 Partition the data into training, validation and test data.

I will split the dataset into three parts to prevent data leakage: - Training set: used to train the predictive model. - Validation set: used for model fine-tuning and hyperparameter selection. - Test set: used to evaluate the final performance. To prevent the dependency on how train test are splitted, I will use 10-fold cross-validation in the modeling part to enhance the robustness of the model.

set.seed(267) 

df$card_type <- factor(df$card_type, levels = c("nonbuyers", "buyers"))
# create stratified training and temp sets
index_train <- createDataPartition(df$card_type, p = 0.8, list = FALSE)

# keep a copy of test set with client_id for later Top-N clients.
# drop client_id for both train and testset, as it is not relevant to modeling.
df_train <- df[index_train, ] %>% select(-client_id)
df_test_clientid <- df[-index_train, ] 
df_test <- df_test_clientid %>% select(-client_id)

2 Classify the data by machine learning algorithms

2.1 Baseline model: logistic regression

I will use logistic regression model as a baseline. Logistic regression requires numeric input variables. Therefore, I will first transform the non-numeric variables into numeric format.

unique_data_types <- unique(sapply(df, class))
cat("Data types of df:", paste(unique_data_types, collapse = ", "))
Data types of df: integer, factor, character, numeric
cat_col <- colnames(df %>% select_if(is.character))
cat("Categorical variables and number of unique categories:\n")
Categorical variables and number of unique categories:
sapply(df[, cat_col], function(col) length(unique(col)))
       gender district_name        region  account_freq loan_issuance 
            2            77             8             3             2 

Categorical variables must be encoded for logistic regression. I will convert the binary features (card_type, gender, loan_issuance) directly to integer 1 and 0. For the multi-class features (district_name, region, account_freq), I will apply one-hot encoding to categorical features to avoid assigning the converted number any ordinal meanings.

There are 77 unique categories of district_name, this means, 77 columns will be generated by one-hot-encoding the feature district_name. In total, we will get more than 140 featuers. Depending on the machine learning selection, this might be not problematic. Tree-type models like decision tree and random forest could set tree depth to limit the usage of variables. Logistic regression could use regularization (e.g. L1) to automatically select relevant features and set others as 0.

2.1.1 One hot encoding the categorical variables

df_encoded <- df %>%
  mutate(gender = ifelse(gender == "male", 1, 0),
         loan_issuance = ifelse(loan_issuance == "Yes", 1, 0))

df_encoded$card_type <- factor(df_encoded$card_type, levels = c("nonbuyers", "buyers"))
#encode the categorical variables
encoded_df <- data.frame(model.matrix(~ 0 + ., data = df_encoded[, c("district_name", "region", "account_freq")]))

# remove categorical variables
df_ <- df_encoded[, -which(names(df_encoded) %in% c("district_name", "region", "account_freq"))]
# full data with encoded categorical variables
df_encoded <- cbind(df_, encoded_df)
df_encoded

After One-hot-encoding, the data includes 143 independent features.

2.1.2 split encoded dataframe to train and testset

# drop client_id in both train and test set.
df_train_encoded <- df_encoded[index_train,] %>% select(-client_id)
df_test_encoded <- df_encoded[-index_train,] %>% select(-client_id)
df_test_encoded$card_type <- factor(df_test_encoded$card_type, levels = levels(df_train_encoded$card_type))
x_train_encoded <- df_train_encoded %>% select(-(card_type))
x_test_encoded <- df_test_encoded %>% select(-(card_type))

Split train and test dataset using same data indices as for the original data partitioning. So that it is comparable with later models.

2.1.3 add class weights to the model

As previously already analysed, the dataset is imbalanced, if directly using imbalanced dataset for classification may result bias toward the majority class, causing poor predictions for the minority class.

total_samples <- length(df_train$card_type)
n_buyers = length(df_train$card_type[df_train$card_type == 'buyers'])
n_nonbuyers = total_samples - n_buyers
weight_buyers <- total_samples / (2 * n_buyers)
weight_nonbuyers <- total_samples/ (2 * n_nonbuyers)
cat("There are ",n_buyers,"buyers,", n_nonbuyers, "nonbuyers\n")
There are  598 buyers, 3003 nonbuyers
cat("Weights of buyers:", weight_buyers, "\n")
Weights of buyers: 3.01087 
cat("Weights of non-buyers:", weight_nonbuyers, "\n")
Weights of non-buyers: 0.5995671 
class_weights <- ifelse(df_train$card_type == 'buyers', weight_buyers, weight_nonbuyers)

2.1.4 train logistic regression model with 10-fold cross validation

ctrl <- trainControl(
  method = "cv", 
  number = 10, 
  summaryFunction = twoClassSummary, 
  classProbs = TRUE)
lr_model <- train(x= x_train_encoded, 
                   y = df_train_encoded$card_type, 
                   method = "glm", trControl = ctrl, metric = "ROC", 
                   maxit = 10000,weights = class_weights)
Warning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: prediction from a rank-deficient fit may be misleadingWarning: prediction from a rank-deficient fit may be misleadingWarning: non-integer #successes in a binomial glm!Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred

the warning suggests some of the variables are highly correlated with others and may contribute to multicollinearity. So I will identify these variables and drop them.

2.1.5 drop the highly correlated variables to solve multicollinearity

My method is to find out the eigenvalues that are close to zero or significantly smaller than the others. These low eigenvalues indicate that the corresponding PCs (variables) are highly correlated with others and may be contributing to multicollinearity. Variables with low eigenvalues contribute less to explaining the variance in the data and are less informative. As a result, they may be more likely to overlap in terms of the information they provide, leading to multicollinearity.

Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
Please use `all_of()` or `any_of()` instead.
# Was:
data %>% select(drop_variable_names)

# Now:
data %>% select(all_of(drop_variable_names))

See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.

2.1.6 Scale the data

It is a common preprocessing step in machine learning, including logistic regression, to ensure that all features have the same scale and contribute equally to the model’s performance.

# Define the preprocessing method (scaling to [0, 1])
preprocess_method <- preProcess(df_train_encoded_reduced, method = c("range"))
# Apply the preprocessing method to both the training and test sets
df_train_encoded_reduced_scaled <- predict(preprocess_method, df_train_encoded_reduced)
df_test_encoded_reduced_scaled <- predict(preprocess_method, df_test_encoded_reduced)
x_train_encoded_reduced_scaled <- df_train_encoded_reduced_scaled[,-which(names(df_train_encoded_reduced_scaled) == "card_type")]
x_test_encoded_reduced_scaled <- df_test_encoded_reduced_scaled[,-which(names(df_test_encoded_reduced_scaled) == "card_type")]

2.1.7 train logistic regression on the scaled data

2.1.7.1 function to calculate metrics for the prediction results of each model
metrics_table <- function(pred_labels, true_labels, idx, return_conf_matrix = FALSE){
  expected_levels <- c("nonbuyers", "buyers")
  pred_labels <- factor(pred_labels, levels = expected_levels)
  true_labels <- factor(true_labels, levels = expected_levels)
  conf_matrix <- confusionMatrix(pred_labels, true_labels, positive = 'buyers')
  true_int <- ifelse(true_labels == 'nonbuyers', 0, 1)
  pred_int <- ifelse(pred_labels == 'nonbuyers', 0, 1)
  
  TP <- conf_matrix$table['buyers','buyers']  # True Positives
  TN <- conf_matrix$table['nonbuyers','nonbuyers'] # True Negatives
  FP <- conf_matrix$table['buyers','nonbuyers']  # False Positives
  FN <- conf_matrix$table['nonbuyers','buyers'] # False Negatives
  roc_obj <- roc(true_int, pred_int)
  AuC <- auc(roc_obj)

  result <- data.frame(
    model_index = idx,
    f1_score = conf_matrix$byClass["F1"],
    accuracy = conf_matrix$overall["Accuracy"],
    AuC = AuC,
    kohenkappa = conf_matrix$overall["Kappa"],
    precision = conf_matrix$byClass["Pos Pred Value"],
    recall = conf_matrix$byClass["Sensitivity"])
  if(return_conf_matrix){
    return(as.data.frame(conf_matrix$table))
  }
  else{
    return(result)}}
2.1.7.2 hyperparameter tuning
Warning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -53); Convergence for 53th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -47); Convergence for 47th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -52); Convergence for 52th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -46); Convergence for 46th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -53); Convergence for 53th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -47); Convergence for 47th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -52); Convergence for 52th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -46); Convergence for 46th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -53); Convergence for 53th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -47); Convergence for 47th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -52); Convergence for 52th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -46); Convergence for 46th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -53); Convergence for 53th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -47); Convergence for 47th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -52); Convergence for 52th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -46); Convergence for 46th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -53); Convergence for 53th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -47); Convergence for 47th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -52); Convergence for 52th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -46); Convergence for 46th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -50); Convergence for 50th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -48); Convergence for 48th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returnedWarning: from glmnet C++ code (error code -49); Convergence for 49th lambda value not reached after maxit=1000 iterations; solutions for larger lambdas returned

With grid searching, I get evaluation results of 30 models. The data is sorted by testset f1-score, AuC, accuracy, and kohenkappa. The eighth model has the best performance.

To better visualize the result, I will use line plots to visualize the upper table. Line plots are usually used to present trends. However, it’s perfectly fine to use lines to connect points within rows to distinguish between groups and highlight relationships in the data, because it makes the visualization clearer.

metric_order <- c("f1_score_train","f1_score_test","AuC_train","AuC_test","accuracy_train","accuracy_test", "kohenkappa_train", "kohenkappa_test", "precision_train", "precision_test","recall_train","recall_test")
eval_long <- lr_results %>%
  pivot_longer(cols = all_of(metric_order), names_to = 'metrics', values_to = 'values')

ggplot(eval_long, aes(x = factor(metrics, levels = metric_order), y = values, group = model_index_train)) +
geom_line(aes(color = model_index_train)) +
geom_point(aes(color = model_index_train)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab("") +
ggtitle("Logistic regression: Evaluation with 10-fold Cross Validation")

Most of the lines in the plot have very similar values especially in the trainset, except one model with significant higher recall but lower accuracy AuC kohenkappa.

2.1.8 best model and explain it

Based on the table and line plot, I will focus on the top 6 models with largest metric values.

Find the corresponding hyperparameters of the TOP 6 best models.

idx <- lr_results[1:6,'model_index_train']
top_6_parameters <- hyper_grid[idx,]
top_6_parameters

Select the first one as the best logistic regression model and explain it.

final_lr_model <- lr_models_list[[1]]
# explain the model
explainer_lr <- explain(final_lr_model, data = df_train_encoded_reduced_scaled, y=as.numeric(df_train_encoded_reduced_scaled$card_type), label='logistic regression model')
Preparation of a new explainer is initiated
  -> model label       :  logistic regression model 
  -> data              :  3601  rows  19  cols 
  -> target variable   :  3601  values 
  -> predict function  :  yhat.train  will be used (  default  )
  -> predicted values  :  No value for predict function target column. (  default  )
  -> model_info        :  package caret , ver. 6.0.94 , task classification (  default  ) 
  -> predicted values  :  numerical, min =  0.3164876 , mean =  0.490862 , max =  0.8129036  
  -> residual function :  difference between y and yhat (  default  )
  -> residuals         :  numerical, min =  0.2094304 , mean =  0.675203 , max =  1.670787  
  A new explainer has been created!  

2.1.9 Top-10 features

Top 10 features by permutation importance ranking:

set.seed(123)
pfi_lr <- model_parts(explainer_lr, type = "variable_importance", N = 1000)
plot(pfi_lr[1:11,], show_boxplots = TRUE) + ggtitle("Logistic regression: Top 10 most important features")

The plot tells, when mixing up the values of one feature, how much will the model negatively affected. Larger changes indicating higher importance of this feature. For example, the feature order_UVER has the highest importance here. The model with original full features has the AuC of 0.565. When mixing up the values of order_UVER, the model AuC is reduced to 0.539, the difference is 0.026. We could see that, only the top 5 features (order_UVER, order_SIPO, order_OTHER, nr_municipalities_4, nr_enterpreneurs_per_1000_inhabitants) have valid importance/influence/contribution to the model performance. The top 6 to 10 features have almost null influence.

2.1.10 Top-10 clients

Let’s observe the top 10 clients with the highest prediction probabilities.

final_lr_predictions_test <- lr_predictions_test_list[[idx[1]]] 
Top_n_clients_lr <- final_lr_predictions_test %>% arrange(desc(buyers))
Top_n_clients_lr

Overall, the prediction probabilities are not that high, with a maximal of 0.7.

Check exactly which features contributed to the predictions.

n = 10
plot_list <- list()
for (i in seq(1, n)) {
  idx = Top_n_clients_lr[i,3]
  client_id = Top_n_clients_lr[i,2]
  bdp_lr <- predict_parts(explainer_lr, new_observation = df_test_encoded_reduced_scaled[idx, ])
  p <- plot(bdp_lr) + ggtitle(paste("Logistic regression - Client_id ",client_id))
  plot_list[[i]] <- p}

for (i in 1:n) {
  print(plot_list[[i]])
}

In all top 10 clients, the features order_SIPO, order_UVER, order_OTHER are the main contributors.

2.1.11 Evaluation

Receiver Operating Characteristic curve (ROC)

Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0.
Please use `reframe()` instead.
When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly.

The x-axis FPR of ROC represents false positive rate, and the y-axis TPR for true positive rate.

The AUC measures the area underneath the entire ROC curve from (0,0) to (1,1). It is an aggregate measure of the model’s performance across all classification thresholds. An AUC of 0.5 suggests no discriminative power and an AUC of 1.0 represents perfect prediction.

In my ROC graph, the AUC is 0.53, which suggests that the logistic regression model is performing only slightly better than random guessing for the given task.

The confusion matrix tells, over half of the buyers (81 of 149) are false predicted as nonbuyers. Predictions on nonbuyers are slightly better, 257 of 750 nonbuyers are false predicted as buyers.

metric_results

The best logistic regression has weak performance, indicating the relationship between the log odds of the dependent variable (buyers or nonbuyers) and the independent variables is not linear.

2.2 Decision tree

2.2.1 train and tune hyperparameters

Decision tree could handle categorical data and unscaled data, so i will use the original not-encoded data with the same data spliting indices.


Attaching package: ‘rpart’

The following object is masked from ‘package:dials’:

    prune

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases

Visualize the result in lineplots, each line represents one model

eval_long <- tree_results %>%
  pivot_longer(cols = all_of(metric_order), names_to = 'metrics', values_to = 'values')

ggplot(eval_long, aes(x = factor(metrics, levels = metric_order), y = values, group = model_index_train)) +
geom_line(aes(color = model_index_train)) +
geom_point(aes(color = model_index_train)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab("") +
ggtitle("Decision tree: Evaluation with 10-fold Cross Validation")

The lines have very similar shape. The difference between train and test dataset is relatively large, because tree-type model is prone to overfitting.

Check the corresponding hyperparameters of the TOP 6 best models.

idx <- tree_results[1:6,'model_index_train']
top_6_parameters <- hyper_grid[idx,]
top_6_parameters

2.2.2 best decision tree model and explain it

Select the model with index 19 as the best tree model, and explain it.

final_tree_model <- tree_models_list[[1]]
# explainer
explainer_tree <- explain(final_tree_model, data = df_train, y=as.numeric(df_train$card_type), label='tree model')
Preparation of a new explainer is initiated
  -> model label       :  tree model 
  -> data              :  3601  rows  60  cols 
  -> data              :  tibble converted into a data.frame 
  -> target variable   :  3601  values 
  -> predict function  :  yhat.rpart  will be used (  default  )
  -> predicted values  :  No value for predict function target column. (  default  )
  -> model_info        :  package rpart , ver. 4.1.19 , task classification (  default  ) 
  -> predicted values  :  numerical, min =  0.03350785 , mean =  0.166065 , max =  0.6025974  
  -> residual function :  difference between y and yhat (  default  )
  -> residuals         :  numerical, min =  0.3974026 , mean =  1 , max =  1.966492  
  A new explainer has been created!  

2.2.3 Top-10 features: permuatation importance

set.seed(123)
pfi_tree <- model_parts(explainer_tree, type = "variable_importance", N = 1000)
plot(pfi_tree[1:11,], show_boxplots = FALSE) + ggtitle("variable_importance", "")

All top 10 important features have visible influence on the model performance. The sad_trans has significant dominant influence comparing to other features.

2.2.4 Top-10 clients and feature contributions

## Top-N clients
final_tree_predictions_test <- tree_predictions_test_list[[idx[1]]] 
Top_n_clients_tree <- final_tree_predictions_test %>% arrange(desc(buyers))
Top_n_clients_tree

The top clients have very high predicted probabilities, even perfect.

10 features that contributed most to each of the top-10 clients

n = 10
plot_list <- list()
for (i in seq(1, n)) {
  idx = Top_n_clients_tree[i,3]
  client_id = Top_n_clients_tree[i,2]
  bdp_tree <- predict_parts(explainer_tree, new_observation = df_test[idx, ])
  p <- plot(bdp_tree) + ggtitle(paste("Decision tree - Client_id ",client_id))
  plot_list[[i]] <- p}

for (i in 1:n) {
  print(plot_list[[i]])
}

For each client of the top-10, maximal 6 features are contributing to the prediction result.

2.2.5 Evaluation

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = buyers, case = nonbuyers
Setting direction: controls > cases

AUC = 0.85 is relatively large, this means the decision tree prediction is much better than random guessing.

Setting levels: control = 0, case = 1
Setting direction: controls < cases

The confusion matrix indicates, the model predicts better on nonbuyers group than the buyers group (weak recall).

metric_results

The final decision tree model performs better than the baseline logistic regression model.

2.3 Random forest

2.3.1 train model and tune hyperparameter

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
eval_long <- forest_results %>%
  pivot_longer(cols = all_of(metric_order), names_to = 'metrics', values_to = 'values')
ggplot(eval_long, aes(x = factor(metrics, levels = metric_order), y = values, group = model_index_train)) +
geom_line(aes(color = model_index_train)) +
geom_point(aes(color = model_index_train)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab("") +
ggtitle("Random forest: Evaluation with 10-fold Cross Validation")

We see two groups of lines from the plot. Comparing to decision tree, the difference between train and test dataset is smaller. This is because, random forest uses multiple number of decision tree models, to reduce overfitting.

2.3.2 best random forest

Check the exact parameters of the top 6 best random forest models.

idx <- forest_results[1:6,'model_index_train']
top_6_parameters <- hyper_grid[idx,]
top_6_parameters

Best random forest model

final_forest_model <- forest_models_list[[1]]
# explainer
explainer_forest <- explain(final_forest_model, data = df_train, y=as.numeric(df_train$card_type), label='random forest model')
Preparation of a new explainer is initiated
  -> model label       :  random forest model 
  -> data              :  3601  rows  60  cols 
  -> data              :  tibble converted into a data.frame 
  -> target variable   :  3601  values 
  -> predict function  :  yhat.train  will be used (  default  )
  -> predicted values  :  No value for predict function target column. (  default  )
  -> model_info        :  package caret , ver. 6.0.94 , task classification (  default  ) 
  -> predicted values  :  numerical, min =  0 , mean =  0.1038767 , max =  0.82  
  -> residual function :  difference between y and yhat (  default  )
  -> residuals         :  numerical, min =  0.62 , mean =  1.062188 , max =  2  
  A new explainer has been created!  

2.3.3 Top-10 features by permutation importance

set.seed(123)
pfi_forest <- model_parts(explainer_forest, type = "variable_importance", N = 1000)
plot(pfi_forest[1:11,], show_boxplots = FALSE) + ggtitle("variable_importance", "")

The top 10 features are mainly the account_age, and statistics features of transaction and asset. The most important feature account_age has an influence factor of around 0.0008. This is very small comparing to upper two models. This means, the random forest model uses a long list of features candidates with similar importance, where the decision tree and logistic regression relied on only a few features.

2.3.4 Top-10 clients

final_forest_predictions_test <- forest_predictions_test_list[[idx[1]]] 
Top_n_clients_forest <- final_forest_predictions_test %>% arrange(desc(buyers))
Top_n_clients_forest

The probabilities are relatively good, but smaller than the decision tree.

Features contribution analysis for top-10 clients

n = 10
plot_list <- list()
for (i in seq(1, n)) {
  idx = Top_n_clients_forest[i,3]
  client_id = Top_n_clients_forest[i,2]
  bdp_forest <- predict_parts(explainer_forest, new_observation = df_test[idx, ])
  p <- plot(bdp_forest) + ggtitle(paste("Random forest - Client_id ",client_id))
  plot_list[[i]] <- p}

for (i in 1:n) {
  print(plot_list[[i]])
}

For each client, the top 10 contributed features have actually very similar strength of contribution. The all other features together have as well significant contribution. This tells, there are a long list of important features contribute to the final prediction of each client.

2.3.5 Evaluation

AUC = 0.91, indicating a good prediction.

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = buyers, case = nonbuyers
Setting direction: controls > cases

Confusion matrix

Setting levels: control = 0, case = 1
Setting direction: controls < cases

confusion matrix: The random forest could perfectly predict the nonbuyers group, but still over half of the buyers are false predicted as nonbuyers. This means, though with class_weights, the random forest could not perform that well on the minor group.

metric_results

Over the evaluations of all six metrics on testset, random forest has much better performance than decision tree and logistic regression.

2.4 Support vector machine (SVM)

2.4.1 train and tune hyperparameter


Attaching package: ‘e1071’

The following object is masked from ‘package:tune’:

    tune

The following object is masked from ‘package:rsample’:

    permutations

The following object is masked from ‘package:parsnip’:

    tune

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases

Visualize the evaluation result of tuning results.

eval_long <- svm_results %>%
  pivot_longer(cols = all_of(metric_order), names_to = 'metrics', values_to = 'values')

ggplot(eval_long, aes(x = factor(metrics, levels = metric_order), y = values, group = model_index_train)) +
geom_line(aes(color = model_index_train)) +
geom_point(aes(color = model_index_train)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab("") +
ggtitle("SVM: Evaluation with 10-fold Cross Validation")

A few models have larger metric values on testset than on trainset, which is a signal of underfitting. There is one model with significant higher values of all metrics than others and without underfitting sign. This should be the best model. But still I will check the hyperparameters of top 6 best models.

2.4.2 best model

Find the corresponding hyperparameters of the TOP 6 best models.

idx <- svm_results[1:6,'model_index_train']
top_6_parameters <- hyper_grid[idx,]
top_6_parameters

best SVM model and explain it

final_svm_model <- svm_models_list[[1]]
# explainer
explainer_svm <- explain(final_svm_model, data = df_train, y=as.numeric(df_train$card_type), label='svm model')
Preparation of a new explainer is initiated
  -> model label       :  svm model 
  -> data              :  3601  rows  60  cols 
  -> data              :  tibble converted into a data.frame 
  -> target variable   :  3601  values 
  -> predict function  :  yhat.svm  will be used (  default  )
  -> predicted values  :  No value for predict function target column. (  default  )
  -> model_info        :  package e1071 , ver. 1.7.13 , task classification (  default  ) 
  -> predicted values  :  numerical, min =  0.0001697634 , mean =  0.1663878 , max =  0.9999999  
  -> residual function :  difference between y and yhat (  default  )
  -> residuals         :  numerical, min =  0.0016064 , mean =  0.9996772 , max =  1.993597  
  A new explainer has been created!  

2.4.3 Top-10 important features

set.seed(123)
pfi_svm <- model_parts(explainer_svm, type = "variable_importance", N = 1000)
plot(pfi_svm[1:11,], show_boxplots = FALSE) + ggtitle("SVM: Top 10 important features")

The top 10 important features in SVM model are the statistics of transaction and asset. The importance difference between the top 10 features are mild, but all are significant. The top 1 feature max_trans can influence model performance with almost 0.1, around three times large as the top 10 feature var_asset.

2.4.4 Top-10 clients

final_svm_predictions_test <- svm_predictions_test_list[[idx[1]]] 
Top_n_clients_svm <- final_svm_predictions_test %>% arrange(desc(buyers))
Top_n_clients_svm

The predictions of top clients are very high and similar, almost perfect.

2.4.5 Evaluation

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = buyers, case = nonbuyers
Setting direction: controls > cases

ROC graph indicating a good prediction as well.

Plot confusion matrix

Setting levels: control = 0, case = 1
Setting direction: controls < cases

The SVM made much better prediction on buyers group than all other three models. This means, SVM could better handle imbalanced data.

3 Compare the four models and identify the “best” model

3.1 Compare the model performance

metric_results
order <- c("f1_score","AuC", "accuracy","kohenkappa","precision","recall")
metric_results$AuC <- as.numeric(metric_results$AuC)
eval_long <- metric_results %>%
  pivot_longer(cols = all_of(order), names_to = 'metrics', values_to = 'values')

ggplot(eval_long, aes(x = factor(metrics, levels = order), y = values, group = model_index)) +
  geom_line(aes(color = model_index)) +
  geom_point(aes(color = model_index)) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  xlab("") +
  ggtitle("Comparison of four models")

Compare the evaluation metrics of four models: - The performance ranking: SVM > random forest > decision tree >> logistic regression - The SVM (Support Vector Machine) model has the best performance comparing to other three models, as it has the highest F1 score, accuracy, AuC, Cohen’s kappa, recall, and second highest in precision. This possibly due to the SVM strengths: effective in high dimension space, less prone to overfitting, can handle imbalanced data. - The random forest has as well very good performance, it is only slightly worse than SVM. - Decision tree’s performance is worse than random forest, as a single decision tree is prone to overfitting, even though it performs well on trainset, usually hard to generalize well on testset. Random forests reduce variance by averaging multiple deep decision trees, each trained on a different part of the same training set. This results in a more robust model that generalizes better to new data compared to a single decision tree. - The logistic has much worse performance comparing to other three models. The reasons could be logistic regression models data with linear decision boundary. But in this dataset, the relationship between the dependent variable and independent variables is not linear but rather more complex interacted.

3.2 Compare the top 5% and 10% clients for the baseline and

end_index_5 <- as.integer(nrow(df_test)*0.05)
end_index_10 <- as.integer(nrow(df_test)*0.1)
Top_5_clients_lr <- Top_n_clients_lr[1:end_index_5,]$client_id
Top_10_clients_lr <- Top_n_clients_lr[1:end_index_10,]$client_id
Top_5_clients_tree <- Top_n_clients_tree[1:end_index_5,]$client_id
Top_10_clients_tree <- Top_n_clients_tree[1:end_index_10,]$client_id
Top_5_clients_forest <- Top_n_clients_forest[1:end_index_5,]$client_id
Top_10_clients_forest <- Top_n_clients_forest[1:end_index_10,]$client_id
Top_5_clients_svm <- Top_n_clients_svm[1:end_index_5,]$client_id
Top_10_clients_svm<- Top_n_clients_svm[1:end_index_10,]$client_id

top5_lists<- c("Top_5_clients_lr","Top_5_clients_tree","Top_5_clients_forest","Top_5_clients_svm")
top10_lists<- c("Top_10_clients_lr","Top_10_clients_tree","Top_10_clients_forest","Top_10_clients_svm")

Attaching package: ‘reshape2’

The following object is masked from ‘package:tidyr’:

    smiths

  • The overlap percentage of top 10% clients lists between every two models are overall larger than top 5% clients.
  • The overlap clients percentage between models SVM and random forest is largest in both heatmaps, it even reaches 61.8% in top 10% lists. This means, the two models share a more similar view on who are the top clients.
  • The decision has slightly lower overlaps with random forest and SVM.
  • However, the logistic regression has overall very low overlapping clients with all other three models. This indicates that the features the logistic regression model uses for identifing top clients are quite different to decision tree, random forest, SVM models.

Based on the performance and top clients overlaps, I will choose the SVM model as my final best model, as - it has the highest f1-score, accuray, AuC, recall, and second highest precision on unseen new test data. - its predicted top clients are highly overlapped with random forest and decision tree models, this tells, its predictions criterias for identifying top clients are very likely reliable.

4 Global importance of the “best” model and reduce the “best” model by balancing global importance and model performance.

4.1 TOP-10 permuatation importance

set.seed(123)
pfi_svm <- model_parts(explainer_svm, type = "variable_importance", N = 1000)
plot(pfi_svm[1:11,], show_boxplots = FALSE) + ggtitle("SVM: Top 10 important features")

max_trans appears to be the most important feature, followed by sad_trans, sad_asset, slope_trans, upper_quartile_trans, as the top-5. It seems transaction, asset, and account age are very important for classifying buyers and nonbuyers in this case.

4.2 stepwise feature selection

Gradually reduce the number of features based on their importance. Firstly, get the feature names sorted by importance in a vector.

df_pfi_svm <- as.data.frame(pfi_svm) %>%
  group_by(variable) %>%
  summarise(mean_dropout_loss = mean(dropout_loss)) %>% arrange(mean_dropout_loss)%>% slice(2:60) 
sorted_variables <- df_pfi_svm$variable

I will start with including only the top 10 important features for training and predicting the card types. Then including another 3 features in each step

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases
svm_results_test_$AuC <- as.numeric(svm_results_test_$AuC)
df_long <- pivot_longer(svm_results_test_, cols = -model_index, names_to = "variable", values_to = "value")

# Create the line plot
ggplot(df_long, aes(x = model_index, y = value, color = variable)) +
  geom_line() +
  theme_minimal() +
  labs(x = "Step (5 features per step)", y = "matric values", title = "Stepwise feature selection")

At the step 2, model has increased to largest of AuC, f1-score, kohenkappa, and recall. After this step, the metric values are decreased. This means, with the selected features at step 2, the SVM model performs best on testset: With less number of features (step 1), model is likely underfitting. With more number of features (step 3 till 10), model is more overfitting, therefore generalize worse on new data.

selected_features <- cols_list[[2]]
selected_features
 [1] "card_type"            "max_trans"            "n_above_mean_trans"   "sad_trans"           
 [5] "n_above_mean_asset"   "slope_trans"          "mad_trans"            "n_pos_changes_asset" 
 [9] "lower_quartile_asset" "n_neg_changes_trans"  "var_asset"            "sad_asset"           
[13] "n_below_mean_asset"   "n_below_mean_trans"   "mad_asset"            "account_age"         
svm_2 <- rbind(svm_results_test_[1,],metric_results[1,])
svm_2$model_index <- c("16 selected features","all features")
svm_2
eval_long <- svm_2 %>%
  pivot_longer(cols = -model_index, names_to = 'metrics', values_to = 'values')

ggplot(eval_long, aes(x = factor(metrics), y = values, group = model_index)) +
geom_line(aes(color = model_index)) +
geom_point(aes(color = model_index)) +
theme(axis.text.x = element_text(hjust = 1)) +
xlab("") +
ggtitle("Compare the effect of feature selection")

Stepwise feature selection has largely reduced the overfitting, and improved the model generalization. The model performance on unseen dataset is largely increased comparing to the model with all features.

4.3 Final model: SVM with 16 features

4.3.1 confusion matrix

Setting levels: control = 0, case = 1
Setting direction: controls < cases

  • The final model could predict very well of nonbuyers group (19 out of 750 nonbuyers are false predicted as buyers), but predict is slightly worse on the buyers group (57 out of 149 true buyers are falsely predicted as nonbuyers).

4.3.2 Top clients

Finally the top 5% clients with the highest potential:

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases

  • The top 5% clients list (44 clients) included only 1 true nonbuyers.
  • The top 10% clients list (89 clients) included 7 true nonbuyers, this is worse than the top 5% clients prediction.
  • The final model could predict the potential buyers very good. Prediction works especially good on the most top clients (e.g. 5%, or even less), when requiring larger number of potential buyers, the performance will slightly decrease.

Break down profile of the top 10 clients to understand the feature contributions in each case.

final_model <- svm_models_list_[[2]]
df_train_final <- df_train[selected_features]
df_test_final <- df_test[selected_features]
# explainer
explainer_final <- explain(final_model, data = df_train_final, y=as.numeric(df_train_final$card_type), label='svm model')
Preparation of a new explainer is initiated
  -> model label       :  svm model 
  -> data              :  3601  rows  16  cols 
  -> data              :  tibble converted into a data.frame 
  -> target variable   :  3601  values 
  -> predict function  :  yhat.svm  will be used (  default  )
  -> predicted values  :  No value for predict function target column. (  default  )
  -> model_info        :  package e1071 , ver. 1.7.13 , task classification (  default  ) 
  -> predicted values  :  numerical, min =  0.0002017855 , mean =  0.1657384 , max =  0.9999885  
  -> residual function :  difference between y and yhat (  default  )
  -> residuals         :  numerical, min =  0.0954057 , mean =  1.000327 , max =  1.982359  
  A new explainer has been created!  
n = 10
plot_list <- list()
for (i in seq(1, n)) {
  idx = predictions_test[i,3]
  client_id = predictions_test[i,2]
  bdp_final <- predict_parts(explainer_final, new_observation = df_test_final[idx, ])
  p <- plot(bdp_final) + ggtitle(paste("Break down profile - Client_id ",client_id))
  plot_list[[i]] <- p}

for (i in 1:n) {
  print(plot_list[[i]])
}

  • In the top 10 clients, the main contributed feature is either sad_trans, max_trans, or slope_trans.

4.3.3 features permutation importance

set.seed(123)
pfi_final <- model_parts(explainer_final, type = "variable_importance", N = 1000)
plot(pfi_final, show_boxplots = FALSE) + ggtitle("Final SVM model: perfumation importance")

  • Similarly as previously (the SVM model with full features) shown, max_trans appeared to be the most important feature, with a significantly larger importance than others. This means, if the values of max_trans are mixed up, the model performance will be largely reduced.

5 Describe result of the “final” model for the bank staff

After analyzing the 12-month rollup information, my final model found the following factors as the most important ones for identifying potential credit card buyers. It will be helpful for the bank staff to efficiently selling credit cards as well as other bank products.

transaction values and distribution: - maximal transaction: largest single transaction a client has made. A high maximum transaction might suggest a customer who makes large investments or significant purchases and could be interested in credit card or other products for high-value transactions. - sum of absolute difference: it reflects how much a client’s transactions go up and down. If this number is high, it means the customer’s transaction amounts change a lot. This could indicate someone who has irregular income or expenses and might need flexible banking solutions. - slope: it tells if a client’s transactions are generally increasing or decreasing over time. An increasing slope could mean the customer’s financial activity is growing, potentially a good candidate for savings and investment products. - upper quartile: it gives an idea of the client’s upper-range transaction amount. Customers with a high upper quartile might be more likely to buy premium banking products - median absolute deviation: it measures how varied a customer’s transactions are. A high value may indicate a financial life with a lot of ups and downs. - number of transactions are above the mean: If a client often has transactions above their average, they might be experiencing growth or have variable income. - median: it’s a good indicator of what a typical transaction looks like, which can help in understanding a client’s usual financial behavior. - variance: it measures how much a customer’s transactions differ from each other. A high variance means a lot of variability, possibly indicating a customer with unpredictable financial needs.

asset: - sum of absolute difference: similar to transactions, a high SAD of assets suggests the client’s account balance changes frequently, indicating a dynamic financial situation. - robust trend: it indicates whether the client’s assets are consistently increasing, decreasing, or staying about the same over time. A positive trend indicates signal financial growth, the client might be open to investment opportunities. - number of asset are positive: More positive records might indicate a stable or growing financial situation, suggesting they might be good candidates for additional financial products. - minimal value: it is the smallest amount the client has had in their account. It can help understand the customer’s financial lows and possibly offer products that help of lower balances. - standard deviation: it tells how much a client’s account balance varies. A client with a high standard deviation may need services in managing financial risk. - number of asset are above the mean: how often a customer’s account balance is above their average balance. A large number indicates, the client might be a good prospect for savings or investment products due to their habit of maintaining higher balances.

account_age: how long the client has had this account with the bank. A longer account age could indicate loyalty and familiarity with the bank’s services, and these clients might be more receptive to new offers. For the bank staff, these features provide clues about a customer’s financial health and behavior. Understanding these can help in suggesting the right products to the customers, such as savings accounts, investment opportunities, financial planning services, or even special programs for those with high transaction volumes.

LS0tCnRpdGxlOiAiQU1MOiBQcm9kdWN0IGFmZmluaXR5IG1vZGVsaW5nIGJ5IFdlaXBpbmcgWmhhbmcsIEZTMjMiIApvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6ICc0JwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2NfZmxvYXQ6IHRydWUKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogeWVzCiAgICAgIHNtb290aF9zY3JvbGw6IHllcwogICAgdGhlbWU6IHVuaXRlZAogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKIyMjIFNDT1BFOiBmaW5kIHRoZSBwb3RlbnRpYWwgaW1wb3J0YW50IGZlYXR1cmVzIGZvciBjcmVkaXQgY2FyZCBzZWxsaW5nLCBhbmQgcmFuayB0aGUgY2xpZW50cyB3aXRoIHRoZSBoaWdodGVzdCBwcm9iYWJpbGl0aWVzCgpJIHdvdWxkIGxpa2UgdG8gYW5hbHl6ZSBjdXN0b21lciBwcmVmZXJlbmNlcyBhbmQgZmluYW5jaWFsIGJlaGF2aW9ycyB0byB1bmRlcnN0YW5kIHRoZSBwb3RlbnRpYWwgY3Jvc3Mtc2VsbGluZyBvcHBvcnR1bml0aWVzLiBGaW5hbGx5LCB0aGUgcHJlZGljdGlvbiBtb2RlbCBjYW4gaGVscCB0aGUgYmFua3MgdG8gaWRlbnRpZnkgZ29vZCBjbGllbnRzIHdobyBtYXkgYnV5IGNyZWRpdCBjYXJkIGFuZCBiYWQgY2xpZW50cyB3aG8gYXJlIHVubGlrZWx5IHRvIHBheSB0aGUgY3JlZGl0cy4KCgpgYGB7cixtZXNzYWdlPUZBTFNFfQojIExvYWQgdGhlIGxpYnJhcmllcwpsaWJyYXJ5KHRpZHltb2RlbHMpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KERhdGFFeHBsb3JlcikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShybGFuZykKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShjYXJldCkKbGlicmFyeSgicFJPQyIpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KE1hdHJpeCkKbGlicmFyeShST1NFKQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKbGlicmFyeShjYXJldCkKbGlicmFyeShyYW5nZXIpCmxpYnJhcnkoREFMRVgpIApsaWJyYXJ5KERBTEVYdHJhKQpsaWJyYXJ5KGtrbm4pCmBgYAoKCiMjIDEuIERhdGEgcHJlcHJvY2Vzc2luZwojIyMgMS4xIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMKIyMjIyAxLjEuMSBsb2FkIHJlbGV2YW50IGRhdGFzZXQgYW5kIGNoZWNrIGRhdGEgcXVhbGl0eQoKYGBge3J9CmFjY291bnQgPC0gcmVhZC5jc3YoZmlsZSA9ICdkYXRhL2FjY291bnQuY3N2JyxzZXAgPSAnOycpCmNhcmQgPC0gcmVhZC5jc3YoZmlsZSA9ICdkYXRhL2NhcmQuY3N2JyxzZXAgPSAnOycpCmNsaWVudCA8LSByZWFkLmNzdihmaWxlID0gJ2RhdGEvY2xpZW50LmNzdicsc2VwID0gJzsnKQpkaXNwIDwtIHJlYWQuY3N2KGZpbGUgPSAnZGF0YS9kaXNwLmNzdicsc2VwID0gJzsnKQpkaXN0cmljdCA8LSByZWFkLmNzdihmaWxlID0gJ2RhdGEvZGlzdHJpY3QuY3N2JyxzZXAgPSAnOycpCmxvYW4gPC0gcmVhZC5jc3YoZmlsZSA9ICdkYXRhL2xvYW4uY3N2JyxzZXAgPSAnOycpCm9yZGVyIDwtIHJlYWQuY3N2KGZpbGUgPSAnZGF0YS9vcmRlci5jc3YnLHNlcCA9ICc7JykKdHJhbnMgPC0gcmVhZC5jc3YoZmlsZSA9ICdkYXRhL3RyYW5zLmNzdicsc2VwID0gJzsnKQpgYGAKClRoZXJlIGFyZSBlaWdodCBkYXRhZnJhbWVzLCBJIHdpbGwgb2JzZXJ2ZSB0aGVtIG9uZSBhZnRlciBhbm90aGVyIHRvIHVuZGVyc3RhbmQgdGhlbS4KCiMjIyMgMS4xLjIgdW5kZXJzdGFuZCBhbmQgdHJhbnNmb3JtIGRhdGEKIyMjIyMgMS4xLjIuMSBkaXNwb3NpdGlvbjogZWFjaCByZWNvcmQgcmVsYXRlcyB0b2dldGhlciBhIGNsaWVudCB3aXRoIGFuIGFjY291bnQKCmBgYHtyfQpkaXNwIDwtIHVuaXF1ZShkaXNwKQpwcmludChoZWFkKGRpc3AsMykpCmBgYApgYGB7cn0KIyBmdW5jdGlvbiBwcmludF9kZl9pbmZvIHdpbGwgcHJpbnQgc29tZSBiYXNpYyBpbmZvcm1hdGlvbiBvZiBhIGRhdGFmcmFtZQpwcmludF9kZl9pbmZvIDwtIGZ1bmN0aW9uKGRmKSB7CiAgY2F0KGRpbShkZilbMV0sICJyb3dzIGFuZCIsIGRpbShkZilbMl0sICJjb2x1bW5zXG4iKQogIGNhdCgiRGF0YSB0eXBlczpcbiIpCiAgcHJpbnQoc2FwcGx5KGRmLCBjbGFzcykpCiAgY2F0KCJudW1iZXIgb2YgTkFzIGluIGNvbHVtbnM6XG4iKQogIGNhdCgiICAiLCBjb2xTdW1zKGlzLm5hKGRmKSksICJcbiIsIHNlcCA9ICIgICIpCiAgY2F0KHN1bShyb3dTdW1zKGlzLm5hKGRmKSkgPiAwKSwgInJvd3MgY29udGFpbiBtaXNzaW5nIHZhbHVlc1xuIikKCiAgaWYgKGxlbmd0aChkZikgPiAwKSB7CiAgICBmb3IgKGNvbCBpbiBuYW1lcyhkZikpIHsKICAgICAgaWYgKGlzLm51bWVyaWMoZGZbW2NvbF1dKSkgewogICAgICAgIGNhdCgiXG5Db2x1bW4iLCBjb2wsICJcbiIpCiAgICAgICAgcHJpbnQoc3VtbWFyeShkZltbY29sXV0pKQogICAgICB9IGVsc2UgaWYgKGlzLmNoYXJhY3RlcihkZltbY29sXV0pKSB7CiAgICAgICAgY2F0KCJcbkNvbHVtbiIsIGNvbCwgInVuaXF1ZSBWYWx1ZXM6XG4iKQogICAgICAgIHVuaXF1ZV92YWx1ZXMgPC0gdW5pcXVlKGRmW1tjb2xdXSkKICAgICAgICBjYXQoIiAgIiwgcGFzdGUodW5pcXVlX3ZhbHVlcywgY29sbGFwc2UgPSAiLCAiKSwgIlxuIikKICAgICAgfX19IGVsc2Uge2NhdCgiTm8gZGF0YSBpbiB0aGlzIGRhdGFmcmFtZS5cbiIpfX0KCnByaW50X2RmX2luZm8oZGlzcCkKYGBgCgpUaGVyZSBhcmUgbm8gTkFzIG9yIGJsYW5rIGNlbGxzLiBEYXRhIHR5cGVzIGFyZSBjb3JyZWN0LgoKYGBge3J9CiMgZ2VuZXJhdGUgYSBmdW5jdGlvbiB0byBjaGVjayByZWxhdGlvbnNoaXAgYmV0d2VlbiB0d28gdmFyaWFibGVzCmNoZWNrX3JlbGF0aW9uc2hpcCA8LSBmdW5jdGlvbihkYXRhZnJhbWUsIGNvbHVtbjEsIGNvbHVtbjIpIHsKICBpZiAoYWxsKHRhYmxlKGRhdGFmcmFtZVtbY29sdW1uMV1dKSA9PSAxKSAmJiBhbGwodGFibGUoZGF0YWZyYW1lW1tjb2x1bW4yXV0pID09IDEpKSB7CiAgICBjYXQoIlRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiIsIGNvbHVtbjEsICJhbmQiLCBjb2x1bW4yLCAiaXMgb25lLXRvLW9uZS5cbiIpCiAgfSBlbHNlIGlmIChhbnkodGFibGUoZGF0YWZyYW1lW1tjb2x1bW4xXV0pID4gMSkgJiYgYWxsKHRhYmxlKGRhdGFmcmFtZVtbY29sdW1uMl1dKSA9PSAxKSkgewogICAgY2F0KCJUaGUgcmVsYXRpb25zaGlwIGJldHdlZW4iLCBjb2x1bW4xLCAiYW5kIiwgY29sdW1uMiwgImlzIG9uZS10by1tYW55LlxuIikKICB9IGVsc2UgaWYgKGFsbCh0YWJsZShkYXRhZnJhbWVbW2NvbHVtbjFdXSkgPT0gMSkgJiYgYW55KHRhYmxlKGRhdGFmcmFtZVtbY29sdW1uMl1dKSA+IDEpKSB7CiAgICBjYXQoIlRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiIsIGNvbHVtbjEsICJhbmQiLCBjb2x1bW4yLCAiaXMgbWFueS10by1vbmUuXG4iKQogIH0gZWxzZSB7CiAgICBjYXQoIlRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiIsIGNvbHVtbjEsICJhbmQiLCBjb2x1bW4yLCAiaXMgbmVpdGhlciBvbmUtdG8tb25lLCBvbmUtdG8tbWFueSwgbm9yIG1hbnktdG8tb25lLlxuIikKICB9fQoKY2hlY2tfcmVsYXRpb25zaGlwKGRpc3AsICJhY2NvdW50X2lkIiwgImRpc3BfaWQiKQpjaGVja19yZWxhdGlvbnNoaXAoZGlzcCwgImNsaWVudF9pZCIsICJkaXNwX2lkIikKYGBgCgoKIyMjIyMgMS4xLjIuMiBjYXJkOiBlYWNoIHJlY29yZCBkZXNjcmliZXMgYSBjcmVkaXQgY2FyZCBpc3N1ZWQgdG8gYW4gYWNjb3VudAoKYGBge3J9CmNhcmQgPC0gdW5pcXVlKGNhcmQpCnByaW50KGhlYWQoY2FyZCwzKSkKYGBgCmBgYHtyfQojIGNoZWNrIHRoZSBkYXRhIGluZm9ybWF0aW9uIGFuZCBxdWFsaXR5CnByaW50X2RmX2luZm8oY2FyZFssMTozXSkKYGBgCgpgYGB7cn0KIyByZW1vdmUganVuaW9yIGNyZWRpdCBjYXJkIGN1c3RvbWVycywgYmVjYXVzZSBvZiB0aGUgc3BlY2lmaWMgZ29hbHMgYW5kIHN0cmF0ZWdpZXMgb2YgdGhlIGJhbmsKY2FyZCA8LSBjYXJkICU+JSBmaWx0ZXIodHlwZSAhPSAnanVuaW9yJykgCiMgVHJhbnNmb3JtICdpc3N1ZWQnIHRvIGEgRGF0ZSB0eXBlCmNhcmQkaXNzdWVkIDwtIGFzLkRhdGUoY2FyZCRpc3N1ZWQsIGZvcm1hdCA9ICcleSVtJWQnKQojIHJlbmFtZSBjb2x1bW5zCmNvbG5hbWVzKGNhcmQpWzM6NF0gPC0gYygiY2FyZF90eXBlIiwiY2FyZF9pc3N1ZWQiKQpwcmludChoZWFkKGNhcmQsMykpCmBgYAoKYGBge3J9CmNoZWNrX3JlbGF0aW9uc2hpcChjYXJkLCAiY2FyZF9pZCIsICJkaXNwX2lkIikKYGBgCgoKIyMjIyMgMS4xLjIuMyBkaXN0cmljdDogZWFjaCByZWNvcmQgZGVzY3JpYmVzIGRlbW9ncmFwaGljIGNoYXJhY3RlcmlzdGljcyBvZiBhIGRpc3RyaWN0CmBgYHtyfQpkaXN0cmljdCA8LSB1bmlxdWUoZGlzdHJpY3QpCnByaW50KGhlYWQoZGlzdHJpY3QsMykpCmBgYAoKYGBge3J9CiMgYWRkIGNvbHVtbiBuYW1lcwpjb2xuYW1lcyhkaXN0cmljdCkgPC0gYygiZGlzdHJpY3RfaWQiLCJkaXN0cmljdF9uYW1lIiwicmVnaW9uIiwibnJfaW5oYWJpdGFudHMiLCJucl9tdW5pY2lwYWxpdGllc18xIiwibnJfbXVuaWNpcGFsaXRpZXNfMiIsIm5yX211bmljaXBhbGl0aWVzXzMiLCJucl9tdW5pY2lwYWxpdGllc180IiwibnJfY2l0aWVzIiwicmF0aW9fdXJiYW5faW5oYWJpdGFudHMiLCJhdmVyYWdlX3NhbGFyeSIsICJ1bmVtcGxveV9yYXRlXzk1IiwidW5lbXBsb3lfcmF0ZV85NiIsIm5yX2VudGVycHJlbmV1cnNfcGVyXzEwMDBfaW5oYWJpdGFudHMiLCJucl9jb21taXRlZF9jcmltZXNfOTUiLCAibnJfY29tbWl0ZWRfY3JpbWVzXzk2IikKcHJpbnQoaGVhZChkaXN0cmljdCwzKSkKYGBgCmBgYHtyfQpwcmludF9kZl9pbmZvKGRpc3RyaWN0KQpgYGAKClRoZSBkYXRhIHR5cGVzIG9mIGNvbHVtbnMgdW5lbXBsb3lfcmF0ZV85NSBhbmQgbnJfY29tbWl0ZWRfY3JpbWVzXzk1IHdlcmUgaW5jb3JyZWN0LiAKQ29udmVydCB0aGVtIGZyb20gY2F0ZWdvcmljYWwgdG8gbnVtZXJpYyB0eXBlLgpUaGUgdHdvIGNvbHVtbnMgY29udGFpbiBhbHNvICc/JyBjaGFyYWN0ZXIsIHRoZXkgc2hvdWxkIGJlIHJlcGxhY2VkIHdpdGggTkEKCmBgYHtyfQojIHJlcGxhY2UgIj8iIHdpdGggTkEKZGlzdHJpY3QkdW5lbXBsb3lfcmF0ZV85NVtkaXN0cmljdCR1bmVtcGxveV9yYXRlXzk1ID09ICI/Il0gPC0gTkEKZGlzdHJpY3QkbnJfY29tbWl0ZWRfY3JpbWVzXzk1W2Rpc3RyaWN0JG5yX2NvbW1pdGVkX2NyaW1lc185NSA9PSAiPyJdIDwtIE5BCiMgdHJhbnNmb21yIGRhdGEgdHlwZSB0byBudW1lcmljCmRpc3RyaWN0JHVuZW1wbG95X3JhdGVfOTUgPC0gYXMubnVtZXJpYyhkaXN0cmljdCR1bmVtcGxveV9yYXRlXzk1KQpkaXN0cmljdCRucl9jb21taXRlZF9jcmltZXNfOTUgPC0gYXMubnVtZXJpYyhkaXN0cmljdCRucl9jb21taXRlZF9jcmltZXNfOTUpCmBgYAoKTm93LCBJIGhhdmUgdG8gZGVhbCB3aXRoIHRoZSBOQXMgaW4gdGhlc2UgdHdvIGNvbHVtbnMuIEFjY29yZGluZyB0byBteSBvYnNlcnZhdGlvbiwgdXNpbmcgdGhlIHJhdGlvIGJldHdlZW4gdW5lbXBsb3lfcmF0ZV85NSBhbmQgdW5lbXBsb3lfcmF0ZV85NiBtYXkgYmUgYSBnb29kIGlkZWEgZm9yIE5BIGltcHV0YXRpb24gaW4gY29sdW1uIHVuZW1wbG95X3JhdGVfOTUuIFVzZSBzYW1lIHN0cmF0ZWd5IGZvciB0aGUgbnJfY29tbWl0ZWRfY3JpbWVzXzk1LgoKRmlyc3QsIGNhbGN1bGF0ZSB0aGUgcmF0aW9zIGFuZCBjaGVjayBob3cgdGhleSBkaXN0cmlidXRlZC4KCmBgYHtyfQp1bmVtcGxveV9yYXRpbyA8LSBkaXN0cmljdCR1bmVtcGxveV9yYXRlXzk1IC8gZGlzdHJpY3QkdW5lbXBsb3lfcmF0ZV85NgpjcmltZXNfcmF0aW8gPC0gZGlzdHJpY3QkbnJfY29tbWl0ZWRfY3JpbWVzXzk1IC8gZGlzdHJpY3QkbnJfY29tbWl0ZWRfY3JpbWVzXzk2CgpwX3VuZW1wbG95IDwtIGdncGxvdChkYXRhLmZyYW1lKHVuZW1wbG95X3JhdGlvID0gdW5lbXBsb3lfcmF0aW8pLCBhZXMoeCA9IHVuZW1wbG95X3JhdGlvKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMCwgZmlsbCA9ICJza3libHVlIikgKwogIGxhYnMoCiAgICBtYWluID0gIiIsCiAgICB4ID0gIlJhdGlvICh1bmVtcGxveV9yYXRlXzk1IC8gdW5lbXBsb3lfcmF0ZV85NikiLAogICAgeSA9ICJGcmVxdWVuY3kiKSArIHRoZW1lX21pbmltYWwoKQpwX2NyaW1lIDwtIGdncGxvdChkYXRhLmZyYW1lKGNyaW1lc19yYXRpbyA9IGNyaW1lc19yYXRpbyksIGFlcyh4ID0gY3JpbWVzX3JhdGlvKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMCwgZmlsbCA9ICJza3libHVlIikgKwogIGxhYnMoCiAgICBtYWluID0gIiIsCiAgICB4ID0gIlJhdGlvIChucl9jb21taXRlZF9jcmltZXNfOTUgLyBucl9jb21taXRlZF9jcmltZXNfOTYpIiwKICAgIHkgPSAiRnJlcXVlbmN5IikgKyB0aGVtZV9taW5pbWFsKCkKZ3JpZC5hcnJhbmdlKHBfdW5lbXBsb3ksIHBfY3JpbWUsIG5jb2w9MSkKYGBgClJhdGlvIG9mIHVuZW1wbG95X3JhdGU6Ckl0IGlzIHNsaWdodGx5IGxlZnQgc2tld2VkIGRpc3RyaWJ1dGVkLiBIb3dldmVyLCB0aGUgZGlzdHJpYnV0aW9uIGlzIGZyb20gb25seSA3NyB2YWx1ZXMuIFRoZSByYXRpbyB2YWx1ZXMgbW9zdGx5IGFyZSBiZXR3ZWVuIDAuNyBhbmQgMC45LiBJIHdpbGwgdXNlIHRoZSBtZWRpYW4gdmFsdWUgb2YgdGhlIHJhdGlvIHRvIGltcHV0ZSB0aGUgTkFzIGluIHVuZW1wbG95X3JhdGVfOTUgY29sdW1uLgoKUmF0aW8gb2YgY29tbWl0ZWRfY3JpbWVzOgpJdCBpcyBsaWdodCByaWdodC1za2V3ZWQgZGlzdHJpYnV0ZWQsIHdpdGggdmFsdWVzIG1vc3RseSBiZXR3ZWVuIDAuOSBhbmQgMS4xLiAKCkkgd2lsbCB1c2UgdGhlIG1lZGlhbiB2YWx1ZXMgb2YgcmF0aW8gdG8gaW1wdXRlIE5Bcy4KYGBge3J9CiMgSW1wdXRlIE5BcyBpbiB1bmVtcGxveV9yYXRlXzk1CmRpc3RyaWN0JHVuZW1wbG95X3JhdGVfOTVbaXMubmEoZGlzdHJpY3QkdW5lbXBsb3lfcmF0ZV85NSldIDwtIGRpc3RyaWN0JHVuZW1wbG95X3JhdGVfOTZbaXMubmEoZGlzdHJpY3QkdW5lbXBsb3lfcmF0ZV85NSldICogbWVkaWFuKHVuZW1wbG95X3JhdGlvLCBuYS5ybSA9IFRSVUUpCiMgSW1wdXRlIE5BcyBpbiBucl9jb21taXRlZF9jcmltZXNfOTUKZGlzdHJpY3QkbnJfY29tbWl0ZWRfY3JpbWVzXzk1W2lzLm5hKGRpc3RyaWN0JG5yX2NvbW1pdGVkX2NyaW1lc185NSldIDwtIGRpc3RyaWN0JG5yX2NvbW1pdGVkX2NyaW1lc185Nltpcy5uYShkaXN0cmljdCRucl9jb21taXRlZF9jcmltZXNfOTUpXSAqIG1lZGlhbihjcmltZXNfcmF0aW8sIG5hLnJtID0gVFJVRSkKY2F0KCJOdW1iZXIgb2YgTkFzIGluIGVhY2ggY29sdW1uOiIsY29sU3Vtcyhpcy5uYShkaXN0cmljdCkpKQpgYGAKCgoKIyMjIyMgMS4xLjIuNCBjbGllbnQ6ZWFjaCByZWNvcmQgZGVzY3JpYmVzIGNoYXJhY3RlcmlzdGljcyBvZiBhIGNsaWVudApgYGB7cn0KY2xpZW50IDwtIHVuaXF1ZShjbGllbnQpCnByaW50KGhlYWQoY2xpZW50LDMpKQpgYGAKYGBge3J9CiMgY2hlY2sgZGF0YSBxdWFsaXR5CnByaW50X2RmX2luZm8oY2xpZW50KQpgYGAKCgotIHRoZSBiaXJ0aF9udW1iZXIgY29sdW1uIGluIGRhdGFmcmFtZSBjbGllbnQgcmVwcmVzZW50cyBmb3IgY2xpZW50J3MgYmlydGhkYXkuIHRoaXMgc2hvdWxkIGJlIHRyYW5zZm9ybWVkIHRvIGRhdGUgdHlwZQotIHRoZSBiaXJ0aF9udW1iZXIgd2l0aCBZWU1NKzUwREQgaXMgZm9yIHdvbWFuLCBzbyBoZXJlIGNvdWxkIGFkZCBhIG5ldyBjb2x1bW4gJ2dlbmRlcicKCmBgYHtyfQpjbGllbnQgPC0gY2xpZW50ICU+JQogIG11dGF0ZSgKICAgIG1pZF90d29fZGlnaXRzID0gYXMubnVtZXJpYyhzdWJzdHIoYmlydGhfbnVtYmVyLCAzLCA0KSksCiAgICBnZW5kZXIgPSBpZmVsc2UobWlkX3R3b19kaWdpdHMgPiA1MCwgImZlbWFsZSIsICJtYWxlIiksICMgYWRkIGdlbmRlcgogICAgYmlydGhfbnVtYmVyXyA9IGlmZWxzZShtaWRfdHdvX2RpZ2l0cyA+IDUwLCBiaXJ0aF9udW1iZXIgLSA1MDAwLCBiaXJ0aF9udW1iZXIpLCAKICAgIGJpcnRoZGF5ID0gYXMuRGF0ZShwYXN0ZTAoIjE5IiwgYmlydGhfbnVtYmVyXyksIGZvcm1hdCA9ICIlWSVtJWQiKSkgIyBhZGQgYmlydGhkYXkgYXMgZGF0ZSB0eXBlCiMgY2hlY2sgaWYgdGhlIHdhbmdsaW5nIGlzIGNvcnJlY3QKcHJpbnQoaGVhZChjbGllbnQsIG4gPSA1KSkKYGBgCmBgYHtyfQojIGl0IHNlZW1zIGNvcnJlY3QsIG5vdyBkcm9wIHRoZSBpcnJlbGF2YW50IGNvbHVtbnMKY2xpZW50IDwtIGNsaWVudCAlPiUgc2VsZWN0KC1jKG1pZF90d29fZGlnaXRzLGJpcnRoX251bWJlcixiaXJ0aF9udW1iZXJfKSkKaGVhZChjbGllbnQsIG4gPSAzKQpgYGAKCmBgYHtyfQpjaGVja19yZWxhdGlvbnNoaXAoY2xpZW50LCAiY2xpZW50X2lkIiwgImRpc3RyaWN0X2lkIikKYGBgCgoKIyMjIyMgMS4xLjIuNSBhY2NvdW50OiBlYWNoIHJlY29yZCBkZXNjcmliZXMgc3RhdGljIGNoYXJhY3RlcmlzdGljcyBvZiBhbiBhY2NvdW50CgpgYGB7cn0KYWNjb3VudCA8LSB1bmlxdWUoYWNjb3VudCkKYWNjb3VudFthY2NvdW50ID09ICIiXSA8LSBOQQpwcmludChoZWFkKGFjY291bnQsMykpCmBgYAoKYGBge3J9CiMgaW5zcGVjdCBkYXRhIHF1YWxpdHkKcHJpbnRfZGZfaW5mbyhhY2NvdW50KQpgYGAKLSAnZGF0ZScgY29sdW1uIHNob3VsZCBiZSB0cmFuc2Zvcm1lZCB0byBkYXRlIHR5cGUKCmBgYHtyfQojIGNvbnZlcnQgdGhlICdkYXRlJyBjb2x1bW4gdG8gRGF0ZSBmb3JtYXQsIHJlbmFtZQphY2NvdW50JGFjY291bnRfZGF0ZSA8LSB5bWQoYWNjb3VudCRkYXRlICsgMTkwMDAwMDApCmFjY291bnQgPC0gc3Vic2V0KGFjY291bnQsIHNlbGVjdCA9IC1kYXRlKQpjb2xuYW1lcyhhY2NvdW50KVszXSA8LSAiYWNjb3VudF9mcmVxIgpwcmludChoZWFkKGFjY291bnQsMykpCmBgYApgYGB7cn0KY2hlY2tfcmVsYXRpb25zaGlwKGFjY291bnQsICJhY2NvdW50X2lkIiwgImRpc3RyaWN0X2lkIikKYGBgCgojIyMjIyAxLjEuMi42IGxvYW46IGVhY2ggcmVjb3JkIGRlc2NyaWJlcyBhIGxvYW4gZ3JhbnRlZCBmb3IgYSBnaXZlbiBhY2NvdW50CgpgYGB7cn0KbG9hbiA8LSB1bmlxdWUobG9hbikKcHJpbnQoaGVhZChsb2FuLDMpKQpgYGAKCmBgYHtyfQojIGluc3BlY3QgZGF0YSBxdWFsaXR5CnByaW50X2RmX2luZm8obG9hbikKYGBgCgonZGF0ZScgY29sdW1uIHNob3VsZCBiZSB0cmFuc2Zvcm1lZCB0byBkYXRlIHR5cGUKCmBgYHtyfQojIGNvbnZlcnQgdGhlICdkYXRlJyBjb2x1bW4gdG8gRGF0ZSBmb3JtYXQsIHJlbmFtZQpsb2FuJGxvYW5fZGF0ZSA8LSB5bWQobG9hbiRkYXRlICsgMTkwMDAwMDApCmxvYW4gPC0gc3Vic2V0KGxvYW4sIHNlbGVjdCA9IC1kYXRlKQpjb2xuYW1lcyhsb2FuKVszOjZdIDwtIGMoImxvYW5fYW1vdW50IiwgImxvYW5fZHVyYXRpb24iLCAibG9hbl9wYXltZW50cyIsImxvYW5fc3RhdHVzIikKcHJpbnQoaGVhZChsb2FuLDMpKQpgYGAKYGBge3J9CmNoZWNrX3JlbGF0aW9uc2hpcChsb2FuLCAibG9hbl9pZCIsICJhY2NvdW50X2lkIikKYGBgCgojIyMjIyAxLjEuMi43IG9yZGVyOiBlYWNoIHJlY29yZCBkZXNjcmliZXMgY2hhcmFjdGVyaXN0aWNzIG9mIGEgcGF5bWVudCBvcmRlcgoKYGBge3J9CiMgZHJvcCBkdXBsaWNhdGVzLCByZXBsYWNlIGJsYW5rIGNlbGxzIHdpdGggTkEKb3JkZXIgPC0gdW5pcXVlKG9yZGVyKQpvcmRlcltvcmRlciA9PSAiIl0gPC0gTkEKcHJpbnQoaGVhZChvcmRlciwzKSkKYGBgCgpgYGB7cn0KIyBpbnNwZWN0IGRhdGEgcXVhbGl0eQpwcmludF9kZl9pbmZvKG9yZGVyKQpgYGAKVGhlcmUgYXJlIGJsYW5rIGNlbGxzIGluIGNvbHVtbiBrX3N5bWJvbCwgc2hvdWxkIGJlIHJlcGxhY2VkIHdpdGggTkEuCgpJbiB0aGlzIGRhdGEsIGtfc3ltYm9sIGFuZCBhbW91bnQgYXJlIHR3byBpbXBvcnRhbnQgZmVhdHVyZXMuIGtfc3ltYm9sIHN0YW5kcyBmb3IgdGhlIHBheW1lbnQgdHlwZXM6CiAgICAtICJQT0pJU1RORSIgc3RhbmRzIGZvciBpbnN1cnJhbmNlIHBheW1lbnQKICAgIC0gIlNJUE8iIHN0YW5kcyBmb3IgaG91c2Vob2xkCiAgICAtICJMRUFTSU5HIiBzdGFuZHMgZm9yIGxlYXNpbmcKICAgIC0gIlVWRVIiIHN0YW5kcyBmb3IgbG9hbiBwYXltZW50CgpTbyBJIHdpbGwgZHJvcCB0aGUgY29sdW1ucyB3aGljaCBhcmUgcHJvYmFibHkgaXJyZWxldmFudCB0byB0aGUgcHJvamVjdDogb3JkZXJfaWQsIGJhbmtfdG8sIGFjY291bnRfdG8uCgpgYGB7cn0Kb3JkZXIka19zeW1ib2xbb3JkZXIka19zeW1ib2wgPT0gIiAiXSA8LSBOQQpvcmRlciA8LSBvcmRlciAlPiUgc2VsZWN0KC1jKG9yZGVyX2lkLCBiYW5rX3RvLCBhY2NvdW50X3RvKSkKY2hlY2tfcmVsYXRpb25zaGlwKG9yZGVyLCAiYWNjb3VudF9pZCIsICJvcmRlcl9pZCIpCmBgYApgYGB7cn0KcHJpbnQoaGVhZChvcmRlciwzKSkKYGBgCmNvbnZlcnQgdG8gYSB3aWRlIHRhYmxlLCBzbyB0aGF0IHRoZXJlIGlzIG9ubHkgb25lIG9ic2VydmF0aW9uIHBlciBhY2NvdW50X2lkIApgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyByZXBsYWNlIHRoZSBOQSBhcyAnT1RIRVInIGtfc3ltYm9sCm9yZGVyJGtfc3ltYm9sW2lzLm5hKG9yZGVyJGtfc3ltYm9sKV0gPC0gIk9USEVSIgojIGxvbmcgdG8gd2lkZQpvcmRlciA8LSBvcmRlciAlPiUgZ3JvdXBfYnkoYWNjb3VudF9pZCwga19zeW1ib2wpICU+JSBzdW1tYXJpc2UodG90YWxfYW1vdW50ID0gc3VtKGFtb3VudCkpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBrX3N5bWJvbCwgdmFsdWVzX2Zyb20gPSB0b3RhbF9hbW91bnQsIHZhbHVlc19maWxsID0gMCkKIyBnZW5lcmF0ZSBjb2x1bW4gJ1RPVEFMJyB3aGljaCBpcyB0aGUgc3VtIG9mIG90aGVyIGNvbHVtbnMKb3JkZXIkVE9UQUwgPC0gcm93U3VtcyhvcmRlclssIDI6Nl0sIG5hLnJtID0gVFJVRSkKbmFtZXMob3JkZXIpWy0xXSA8LSBwYXN0ZTAoIm9yZGVyXyIsIG5hbWVzKG9yZGVyKVstMV0pCnByaW50KGhlYWQob3JkZXIsMykpCmBgYAoKCiMjIyMjIDEuMS4yLjggdHJhbnM6IGVhY2ggcmVjb3JkIGRlc2NyaWJlcyBvbmUgdHJhbnNhY3Rpb24gb24gYW4gYWNjb3VudApgYGB7cn0KIyBkcm9wIGR1cGxpY2F0ZXMKdHJhbnMgPC0gdW5pcXVlKHRyYW5zKQpwcmludChoZWFkKHRyYW5zLDMpKQpgYGAKCmBgYHtyfQojIGluc3BlY3QgZGF0YSBxdWFsaXR5CnByaW50X2RmX2luZm8odHJhbnMpCmBgYAoKRm9sbG93aW5nIHByb2JsZW1zIG5lZWQgdG8gYmUgc29sdmVkOgogIC0gRGF0YXR5cGUgb2YgJ2RhdGUnIG5lZWQgdG8gY2hhbmdlZCBmcm9tIGludGVnZXIgdG8gZGF0ZSB0eXBlLgogIC0gVGhlcmUgYXJlIG1hbnkgTkFzLgogIC0gJ3R5cGUnIGNvbHVtbiBzaG91bGQgYmUgZWl0aGVyICdQUklKRU0nIG9yICdWWURBSicgdmFsdWVzIGJhc2VkIG9uIHRoZSByZWZlcmVuY2UuIEhvdyBzaG91bGQgZGVhbCB3aXRoICdWWUJFUicuIAogIC0gJ29wZXJhdGlvbicsICdrX3N5bWJvbCcsICdiYW5rJywgJ2FjY291bnQnIGNvbHVtbnMgaGF2ZSBibGFuayBjZWxscy4KCgpgYGB7cn0KIyAnZGF0ZScgY29sdW1uIHNob3VsZCBiZSB0cmFuc2Zvcm1lZCB0byBkYXRlIHR5cGUuCnRyYW5zJHRyYW5zX2RhdGUgPC0geW1kKHRyYW5zJGRhdGUgKyAxOTAwMDAwMCkKdHJhbnMgPC0gc3Vic2V0KHRyYW5zLCBzZWxlY3QgPSAtZGF0ZSkKCiMgcmVwbGFjZSAiIiBhbmQgIiAiIHdpdGggTkEKdHJhbnNbdHJhbnMgPT0gIiJdIDwtIE5BCnRyYW5zJGtfc3ltYm9sW3RyYW5zJGtfc3ltYm9sID09ICIgIl0gPC0gTkEKcHJpbnQoaGVhZCh0cmFucywzKSkKYGBgCmBgYHtyfQpuYV9jb3VudHMgPC0gY29sU3Vtcyhpcy5uYSh0cmFucykpCm5hX3Byb3BvcnRpb25zIDwtIG5hX2NvdW50cyAvIG5yb3codHJhbnMpCgpiYXJwbG90KG5hX3Byb3BvcnRpb25zLCBuYW1lcy5hcmcgPSBuYW1lcyhuYV9wcm9wb3J0aW9ucyksIGNvbCA9ICJza3libHVlIiwgCiAgICAgICAgbWFpbiA9ICJQcm9wb3J0aW9uIG9mIE5BIFZhbHVlcyBieSBDb2x1bW4iLCB5bGFiID0gIlByb3BvcnRpb24iKQpgYGAKCgpUaGVyZSBhcmUgTkFzIGluIGNvbHVtbnMgb3BlcmF0aW9uIChhcm91bmQgMTklIE5BcyksIGtfc3ltYm9sIChhcm91bmQgNTAlIE5BcyksIGJhbmsgKGFyb3VuZCA3OCUgTkFzKSwgYWNjb3VudCAoYXJvdW5kIDc2JSBOQXMpLgpOb3cgbGV0J3MgdW5kZXJzdGFuZCB0aGUgbWVhbmluZyBvZiB0aGUgY29sdW1ucyBhbmQgaG93IHRvIGRlYWwgd2l0aCB0aGUgTkEgZGF0YSAoaW1wdXRlIE5BIG9yIGRyb3AgdGhlbSkuCgonb3BlcnRpb24nIGNvbHVtbjogc3RvcmVzIHRoZSBtb2RlIG9mIHRyYW5zYWN0aW9uLiAiVllCRVIgS0FSVE9VIiBjcmVkaXQgY2FyZCB3aXRoZHJhd2FsOyAiVktMQUQiIGNyZWRpdCBpbiBjYXNoOyAiUFJFVk9EIFogVUNUVSIgY29sbGVjdGlvbiBmcm9tIGFub3RoZXIgYmFuazsgIlZZQkVSIiB3aXRoZHJhd2FsIGluIGNhc2g7ICJQUkVWT0QgTkEgVUNFVCIgcmVtaXR0YW5jZSB0byBhbm90aGVyIGJhbmsuCgona19zeW1ib2wnIGNvbHVtbjogY2hhcmFjdGVyaXphdGlvbiBvZiB0aGUgdHJhbnNhY3Rpb24uICJQT0pJU1RORSIgc3RhbmRzIGZvciBpbnN1cnJhbmNlIHBheW1lbnQ7ICJTTFVaQlkiIHN0YW5kcyBmb3IgcGF5bWVudCBmb3Igc3RhdGVtZW50OyAiVVJPSyIgc3RhbmRzIGZvciBpbnRlcmVzdCBjcmVkaXRlZDsgIlNBTktDLiBVUk9LIiBzYW5jdGlvbiBpbnRlcmVzdCBpZiBuZWdhdGl2ZSBiYWxhbmNlOyAiU0lQTyIgc3RhbmRzIGZvciBob3VzZWhvbGQ7ICJEVUNIT0QiIHN0YW5kcyBmb3Igb2xkLWFnZSBwZW5zaW9uOyAiVVZFUiIgc3RhbmRzIGZvciBsb2FuIHBheW1lbnQuIAoKJ2JhbmsnIGNvbHVtbjogYmFuayBvZiB0aGUgcGFydG5lci4KCidhY2NvdW50JyBjb2x1bW46IGFjY291bnQgb2YgdGhlIHBhcnRuZXIuCgpJdCBzZWVtcywgdGhlIGNvbnRlbnQgb2Yga19zeW1ibywgYmFuaywgYWNjb3VudCBhcmUgZGVwZW5kZW50IG9uIHRoZSBvcGVyYXRpb24gdHlwZS4gY2hlY2sgaG93IHRoZXkgYXJlIHJlbGF0ZWQuCmBgYHtyfQojIGNvdW50IHRoZSBOQSBieSBvcGVyYXRpb24gdHlwZQp0cmFucyAlPiUgZ3JvdXBfYnkob3BlcmF0aW9uKSAlPiUKICBzdW1tYXJpc2UoCiAgICBUb3RhbF9Db3VudCA9IG4oKSwKICAgIE5BX2NvdW50X2sgPSBzdW0oaXMubmEoa19zeW1ib2wpKSwKICAgIE5BX2NvdW50X2JhbmsgPSBzdW0oaXMubmEoYmFuaykpLAogICAgTkFfY291bnRfYWNjb3VudCA9IHN1bShpcy5uYShhY2NvdW50KSkpICU+JSBwcmludCgpCmBgYApGcm9tIHRoZSB1cHBlciB0YWJsZSwgd2UgY291bGQgc2VlIHRoYXQ6IAogIC0gdGhvdWdoIHRoZXJlIGFyZSAxODMxMTQgTkFzIGluIGNvbHVtbiBvcGVyYXRpb24uIEJ1dCBpbiB0aGVzZSAxODMxMTQgcm93cywgdGhlIGtfc3ltYm9sIGNvbHVtbiB2YWx1ZXMgYXJlIGFsbCBhdmFpbGFibGUgLCBiYW5rIGFuZCBhY2NvdW50IGNvbHVtbnMgYXJlIGFsbCBOQXMuCiAgLSBWWUJFUiBLQVJUT1Ugb3BlcmF0aW9uIGhhcyBvbmx5ICdhY2NvdW50JyBpbmZvcm1hdGlvbiBhdmFpbGFibGUuCiAgLSBQUkVWT0QgWiBVQ1RVIGFuZCBQUkVWT0QgTkEgVUNFVCBvcGVyYXRpb25zIGhhdmUgYWxtb3N0IG5vIE5BcyBpbiBiYW5rIGFuZCBhY2NvdW50IGNvbHVtbnMuCiAgLSBZS0xBRCBvcGVyYXRpb24gaGFzIG5vIGF2YWlsYWJsZSBrX3N5bWJvbCwgYmFuaywgYWNjb3VudCBpbmZvcm1hdGlvbi4KICAKTXkgdW5kZXJzdGFuZGluZyBpczogCiAgLSBUaGUgYXZhaWxhYmlsaXR5IG9mIGNvbHVtbnMgJ2JhbmsnIGFuZCAnYWNjb3VudCcgaXMgbGFyZ2VseSBkZXBlbmRlbnQgb24gJ29wZXJhdGlvbicgdHlwZS4gVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGVzZSB0d28gY29sdW1ucyBtYXkgbm90IHByb3ZpZGUgbWVhbmluZ2Z1bCBpbmZvcm1hdGlvbiBhY3Jvc3MgYWxsICdvcGVyYXRpb24nIHR5cGVzLgogIC0gSW4gYWRkaXRpb24sIG92ZXIgNzUlIG9mIHRoZXNlIHR3byBjb2x1bW5zIGFyZSBub3QgYXZhaWxhYmxlLiBUaGlzIGluZGljYXRlcyB0aGF0IGltcHV0aW5nIHRoZSBtaXNzaW5nIHZhbHVlcyBtaWdodCBub3QgcHJvdmlkZSBhY2N1cmF0ZSByZXN1bHRzLgogIC0gU28gSSB3aWxsIGRyb3AgdGhlIGNvbHVtbnMgJ2JhbmsnIGFuZCAnYWNjb3VudCcuCiAgCmBgYHtyfQp0cmFucyA8LSBzdWJzZXQodHJhbnMsIHNlbGVjdCA9IC1jKGJhbmssIGFjY291bnQpKQpgYGAKCkhvdyBzaG91bGQgSSBkZWFsIHdpdGggY29sdW1uIGtfc3ltYm9sPwpGaXJzdCBjaGVjayB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gb3BlcmF0aW9uIGFuZCBrX3N5bWJvbC4KYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnRyYW5zICU+JWdyb3VwX2J5KG9wZXJhdGlvbiwga19zeW1ib2wpICU+JSBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSBwcmludCgpCmBgYApUaGUgY29sdW1ucyAnb3BlcmF0aW9uJyBhbmQgJ2tfc3ltYm9sJyBoYXZlIDE0IHVuaXF1ZSBjb21iaW5hdGlvbnMuIEFsbCBjb21iaW5hdGlvbnMgc2VlbSByZWFzb25hYmxlIGFuZCB3aXRoIGxhcmdlIGNvdW50IChleGNlcHQgVllCRVItUE9KSVNUTkUpLiBIb3dldmVyLCB0aGVyZSBpcyBub3QgZW5vdWdoIGluZm9ybWF0aW9uIHRvIGltcHV0ZSB0aGUgTkFzIGluIHRoZSB0d28gY29sdW1ucy4gU28gSSB3aWxsIGJpbmQgdGhlIHR3byBjb2x1bW5zIGFzIG9uZSB2YXJpYWJsZSAodHJhbnNfZGV0YWlsKSB0byBzb2x2ZSB0aGUgTkEgaXNzdWUgCgpgYGB7cn0KdHJhbnMgPC0gdHJhbnMgJT4lIG11dGF0ZShvcGVyYXRpb24gPSBpZmVsc2UoaXMubmEob3BlcmF0aW9uKSwgJyAnLCBvcGVyYXRpb24pLCAgIyByZXBsYWNlIE5BcyBhcyBibGFuayBiZWZvcmUgYmluZGluZwogICAgICAgICBrX3N5bWJvbCA9IGlmZWxzZShpcy5uYShrX3N5bWJvbCksICcgJywga19zeW1ib2wpLAogICAgICAgICB0cmFuc19kZXRhaWwgPSBwYXN0ZShvcGVyYXRpb24sIGtfc3ltYm9sLCBzZXAgPSAiLSIpKSAlPiUKICBzZWxlY3QoLW9wZXJhdGlvbiwgLWtfc3ltYm9sKSAKcHJpbnQoaGVhZCh0cmFucywzKSkKYGBgCgpOb3cgSSB3b3VsZCBjaGVjayB0aGUgZGF0YSB3aGljaCB3ZXJlIGluY29ycmVjdGx5IGxhYmVsZWQgYXMgJ1ZZQkVSJy4KYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnRyYW5zICU+JWdyb3VwX2J5KHR5cGUsIHRyYW5zX2RldGFpbCkgJT4lIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgICU+JSBwcmludCgpCmBgYApBbGwgcm93cyB3aXRoICd0eXBlJyBvZiAnVllCRVInIGhhdmUgJ3RyYW5zX2RldGFpbCcgb2YgJ1ZZQkVSLScgKHdpdGhkcmF3YWwgaW4gY2FzaCkuIEFsbCBvdGhlciByb3dzIHdoaWNoIGFyZSBsYWJlbGVkIGFzICd0cmFuc19kZXRhaWwnIG9mICdWWUJFUi0nIGhhdmUgdGhlICd0eXBlJyBvZiAnVllEQUonICh3aXRoZHJhd2FsKSwgd2hpY2ggbWFrZXMgdG90YWxseSBzZW5zZS4gSSB3aWxsIHJlcGxhY2UgYWxsIHRoZSBpbmNvcnJlY3RseSBsYWJlbGVkICdWWUJFUicgaW4gY29sdW1uICd0eXBlJyB3aXRoICdWWURBSicuCmBgYHtyfQp0cmFucyA8LSB0cmFucyAlPiUKICBtdXRhdGUodHlwZSA9IGlmZWxzZSh0eXBlID09ICdWWUJFUicsICdWWURBSicsIHR5cGUpKQpwcmludChoZWFkKHRyYW5zLDMpKQpgYGAKVGhlIGFtb3VudCBjb2x1bW4gaW4gdGhlIHRyYW5zYWN0aW9uIGRhdGEgaXMgYWJzb2x1dGUgdHJhbnNhY3Rpb24gdmFsdWVzLCB0aGlzIG1lYW5zLCBkZXBvc2l0cyAocG9zaXRpdmUpIGFuZCB3aXRoZHJhd2FscyAobmVnYXRpdmUpIHRyYW5zYWN0aW9ucyB3aWxsIGFsbCBiZSBzaG93biBhcyBwb3NpdGl2ZS4gQWJzb2x1dGUgdHJhbnNhY3Rpb24gYW1vdW50IHJlZmxlY3RzIHRoZSB0b3RhbCBmaW5hbmNpYWwgZmxvdywgYW5kIGNvdWxkIGJlIHVzZWZ1bCBmb3IgYXNzZXNzaW5nIGFuIGNsaWVudCdzIG92ZXJhbGwgZmluYW5jaWFsIGFjdGl2aXR5LiBIb3dldmVyLCBpdCBjYW4gbm90IGNhcHR1cmUgdGhlIHRyYW5zYWN0aW9uIGRpcmVjdGlvbi4KU28gYWRkaXRpb25hbGx5LCBJIHdpbGwgYWxzbyBpbnRyb2R1Y2UgdGhlIHJlYWwgYW1vdW50IHZhcmlhYmxlIChpbmNsdWRpbmcgJysvLScpIHRvIHVuZGVyc3RhbmQgc2F2aW5ncyBvciBzcGVuZGluZyBwYXR0ZXJucyBvZiBjbGllbnRzLiBUaGUgaWRlbnRpZmljYXRpb24gaXMgYmFzZWQgb24gdGhlIGJhbGFuY2UgKGluY3JlYXNlZC9kZWNyZWFzZWQgYWZ0ZXIgYWRkaW5nL21pbnVzaW5nIGEgdHJhbnNhY3Rpb24pOgoKYGBge3J9CnRyYW5zIDwtIHRyYW5zICU+JQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JSBhcnJhbmdlKGFjY291bnRfaWQsIHRyYW5zX2RhdGUpICU+JQogIG11dGF0ZShkaWZmZXJlbmNlID0gYmFsYW5jZSAtIGxhZyhiYWxhbmNlLCBkZWZhdWx0ID0gZmlyc3QoYmFsYW5jZSkpLAogICAgYGFtb3VudCgrLy0pYCA9IGlmZWxzZShkaWZmZXJlbmNlID4gMCwgYW1vdW50LCBhbW91bnQgKiAtMSkpICU+JSBzZWxlY3QoLWRpZmZlcmVuY2UpCnByaW50KGhlYWQodHJhbnMsMykpCmBgYAoKYGBge3J9CmNoZWNrX3JlbGF0aW9uc2hpcCh0cmFucywgImFjY291bnRfaWQiLCAidHJhbnNfaWQiKQpgYGAKCgojIyMgMS4yIEpvaW4gYWxsIGRhdGFmcmFtZXMgdG8gY29tYmluZSBjdXN0b21lcnMgaW5mb3JtYXRpb24gYW5kIGJhbmtpbmcgc2VydmljZXMuCgpCZWZvcmUgam9pbmluZyBhbGwgZGF0YWZyYW1lcywgSSB3b3VsZCBsaWtlIHRvIHN1bW1hcml6ZSBzb21lIGltcG9ydGFudCByZWxhdGlvbnNoaXBzLgotIGFjY291bnRfaWQgYW5kIGRpc3RyaWN0X2lkOiBtYW55LXRvLW9uZQotIGFjY291bnRfaWQgYW5kIGNsaWVudF9pZDogb25lLXRvLW1hbnkKLSBhY2NvdW50X2lkIGFuZCB0cmFuc19pZDogb25lLXRvLW1hbnkKLSBhY2NvdW50X2lkIGFuZCBvcmRlcl9pZDogb25lLXRvLW1hbnkKLSBhY2NvdW50X2lkIGFuZCBsb2FuX2lkOiBvbmUtdG8tb25lCi0gYWNjb3VudF9pZCBhbmQgZGlzcF9pZDogb25lLXRvLW1hbnkKLSBjbGllbnRfaWQgYW5kIGRpc3BfaWQ6IG9uZS10by1vbmUKLSBjbGllbnRfaWQgYW5kIGRpc3RyaWN0X2lkOiBtYW55LXRvLW9uZQotIGNhcmRfaWQgYW5kIGRpc3BfaWQgaXMgb25lLXRvLW9uZQoKCmBgYHtyfQojIEZpcnN0IGpvaW4gdGhlIGRhdGFmcmFtZXMgZGlzcG9zaXRpb24sIGNhcmQsIGNsaWVudCwgZGlzdHJpY3QsIGFjY291bnQsIG9yZGVyLgpkZjEgPC0gZGlzcCAlPiUgbGVmdF9qb2luKGNhcmQsIGJ5ID0gImRpc3BfaWQiLCBzdWZmaXggPSBjKCJfZGlzcCIsIl9jYXJkIikpICU+JSBzZWxlY3QoLWMoY2FyZF9pZCxkaXNwX2lkKSkgJT4lIAogIGxlZnRfam9pbihjbGllbnQsYnkgPSAiY2xpZW50X2lkIixzdWZmaXggPSBjKCJfY2FyZCIsIl9jbGllbnQiKSkgICU+JSBsZWZ0X2pvaW4oZGlzdHJpY3QsIGJ5ID0gImRpc3RyaWN0X2lkIikgJT4lIHNlbGVjdCgtZGlzdHJpY3RfaWQpICU+JSBsZWZ0X2pvaW4oYWNjb3VudCwgYnkgPSAiYWNjb3VudF9pZCIpICU+JSBzZWxlY3QoLWRpc3RyaWN0X2lkKSAlPiUgbGVmdF9qb2luKG9yZGVyLCBieSA9ICJhY2NvdW50X2lkIikgJT4lIGxlZnRfam9pbihsb2FuLCBieSA9ICdhY2NvdW50X2lkJywgc3VmZml4ID0gYygiX2FjY291bnQiLCJfbG9hbiIpKQpwcmludChoZWFkKGRmMSwzKSkKYGBgCgoKSXQgd29ydGggbm90aW5nIHRoYXQgb25seSBvd25lciBjYW4gaXNzdWUgcGVybWFuZW50IG9yZGVycyBhbmQgYXNrIGZvciBhIGxvYW4uIFRoaXMgbWVhbnMsIHRoZSBsb2FuIGRhdGFmcmFtZSBzaG91bGQgYmUgam9pbmVkIG9ubHkgdG8gJ09XTkVSJy4gQW5kIEkgd2lsbCBkcm9wIHRoZSAnRElTUE9ORU5UJyB0eXBlIGNsaWVudCwgb25seSBrZWVwIHRoZSAnT1dORVInIHR5cGUgY2xpZW50LiBUbyBrZWVwIHRoZSBpbmZvcm1hdGlvbiAnaG93IG1hbnkgY2xpZW50cyBkb2VzIGFuIGFjY291bnQgaGF2ZSIsIEkgd2lsbCBnZW5lcmF0ZSBhIG5ldyBjb2x1bW4gJ25fY2xpZW50cycgdG8gcmVwcmVzZW50IGl0LiAKCmBgYHtyfQpkZjEgPC0gZGYxICU+JSBncm91cF9ieShhY2NvdW50X2lkKSAlPiUgbXV0YXRlKG5fY2xpZW50cyA9IG4oKSkKCmJhcnBsb3QodGFibGUoZGYxJG5fY2xpZW50cyksIAogICAgICAgIG5hbWVzLmFyZyA9IG5hbWVzKHRhYmxlKGRmMSRuX2NsaWVudHMpKSwgCiAgICAgICAgeGxhYiA9ICJOdW1iZXIgb2YgQ2xpZW50cyBwZXIgQWNjb3VudCIsIHlsYWIgPSAiQ291bnQiLCBtYWluID0gIkRpc3RyaWJ1dGlvbjogTnVtYmVyIG9mIENsaWVudHMgcGVyIEFjY291bnQgIikKYGBgClRoZSBwbG90IHNob3dzIHRoYXQsIGFyb3VuZCAzNzAwIGFjY291bnRfaWQgaGFzIG9ubHkgb25lIGNsaWVudCAoT1dORVIpLCBhcm91bmQgMTgwMCBhY2NvdW50IGhhcyB0d28gY2xpZW50ICgxIE9XTkVSIGFuZCAxIERJU1BPTkVOVCkuCgpgYGB7cn0KIyBjaGVjayBpZiBpdCBpcyB0cnVlIHRoYXQgRElTUE9ORU5UIGNsaWVudHMgaGF2ZSBubyBjcmVkaXQgY2FyZHMuCmRmMSAlPiUgZ3JvdXBfYnkodHlwZSkgJT4lIHN1bW1hcml6ZShjb3VudF9jYXJkID0gc3VtKCFpcy5uYShjYXJkX3R5cGUpKSkgJT4lIHByaW50KCkKYGBgClRoZSByZXN1bHQgY29tZmlybWVkIG15IGFzc3VtcHRpb246IERJU1BPTkVOVCBjbGllbnRzIGhhdmUgbm8gY3JlZGl0IGNhcmRzLiBPbmx5IE9XTkVSIGNsaWVudHMgY291bGQgYmUgaXNzdWVkIGNyZWRpdCBjYXJkcy4gU28gSSB3aWxsIGRyb3AgYWxsICdESVNQT05FTlQnIGNsaWVudHMgcm93cy4gQWZ0ZXJ3YXJkcyB0aGUgY29sdW1uIHR5cGVfZGlzcCB3aWxsIG9ubHkgY29udGFpbiBvbmUgdHlwZSAnT1dORVInLCB0aGlzIG1lYW5zIGl0IGlzIG5vdCBhbnltb3JlIGhlbHBmdWwgZm9yIGZ1cnRoZXIgYW5hbHlzaXMuIFNvIEkgd2lsbCBhbHNvIGRyb3AgdGhlIHdob2xlIGNvbHVtbiB0eXBlX2Rpc3AuCgpgYGB7cn0KZGYxIDwtIHN1YnNldChkZjEsIHR5cGUgIT0gIkRJU1BPTkVOVCIpICU+JSBzZWxlY3QoLXR5cGUsY2xpZW50X2lkLGxvYW5faWQpCmBgYAoKQWdlIGlzIG9mdGVuIGFuIGltcG9ydGFudCBmYWN0b3IgaW4gZmluYW5jaWFsIGNhcGFjaXR5IGFuYWx5c2lzLiBJIHdvdWxkIGFkZCB0d28gdmFyaWFibGVzICdjYXJkX2FnZScgYW5kICdsb2FuX2FnZScgdG8gcmVwcmVzZW50IHRoZSBjbGllbnQgYWdlIHdoZW4gdGhlIGNhcmQgd2FzIGlzc3VlZCwgYW5kIHRoZSBsb2FuIHdhcyBpc3N1ZWQuCgpgYGB7cn0KZGYxJGNhcmRfYWdlIDwtIGFzLm51bWVyaWMoZGlmZnRpbWUoZGYxJGNhcmRfaXNzdWVkLCBkZjEkYmlydGhkYXksIHVuaXRzID0gImRheXMiKSAvIDM2NSkKZGYxJGxvYW5fYWdlIDwtIGFzLm51bWVyaWMoZGlmZnRpbWUoZGYxJGxvYW5fZGF0ZSwgZGYxJGJpcnRoZGF5LCB1bml0cyA9ICJkYXlzIikgLyAzNjUpCnByaW50KGhlYWQoZGYxLDMpKQpgYGAKIAoKIyMjIDEuMyBJZGVudGlmaWNhdGlvbiBvZiBleGlzdGluZyBjcmVkaXQgY2FyZCBidXllcnMgClRoaXMgaW5jbHVkZXMgZGV0ZXJtaW5hdGlvbiBvZiBwdXJjaGFzZSBkYXRlIGFuZCByb2xsdXAgd2luZG93LCBkZWZpbmVkIGJ5IDEgbW9udGggbGFnIGFuZCAxMiBtb250aHMgaGlzdG9yeSBiZWZvcmUgY3JlZGl0IGNhcmQgcHVyY2hhc2UuCgpJZGVudGlmeSB0aGUgZXhpc3RpbmcgY3JlZGl0IGNhcmQgYnV5ZXJzOgpgYGB7cn0KYnV5ZXJzIDwtIHN1YnNldChkZjEsICFpcy5uYShjYXJkX3R5cGUpKSAlPiUgc2VsZWN0KGMoYWNjb3VudF9pZCxjYXJkX3R5cGUsY2FyZF9pc3N1ZWQpKQpub25idXllcnM8LSBzdWJzZXQoZGYxLCBpcy5uYShjYXJkX3R5cGUpKSAlPiUgc2VsZWN0KGMoYWNjb3VudF9pZCxjYXJkX3R5cGUsY2FyZF9pc3N1ZWQpKQpjYXQoIk51bWJlciBvZiBCdXllcnM6IiwgbnJvdyhidXllcnMpLCAiXG4iKQpjYXQoIk51bWJlciBvZiBOb24tQnV5ZXJzOiIsIG5yb3cobm9uYnV5ZXJzKSwgIlxuIikKY2F0KCJSYXRpbyBvZiBOb24tQnV5ZXJzIHRvIEJ1eWVyczoiLCByb3VuZChucm93KG5vbmJ1eWVycykvbnJvdyhidXllcnMpLDEpLCAiXG4iKQpgYGAKYGBge3J9CiMgQ3JlYXRlIGEgdmVjdG9yIG9mIHZhbHVlcwpiYXJwbG90KGMobnJvdyhidXllcnMpLCBucm93KG5vbmJ1eWVycykpLCBuYW1lcy5hcmcgPSBjKCJDcmVkaXQgQ2FyZCBCdXllcnMiLCAiQ3JlZGl0IENhcmQgTm9uLUJ1eWVycyIpLCB5bGFiID0gIkNvdW50IikKYGBgCgpgYGB7cn0KZ2dwbG90KGJ1eWVycywgYWVzKHggPSBjYXJkX2lzc3VlZCkpICsKICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJibHVlIiwgYWxwaGEgPSAwLjcpICsgICMgRGVuc2l0eSBwbG90CiAgbGFicyh4ID0gIklzc3VlZCBEYXRlIiwgeSA9ICJEZW5zaXR5IiwgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIENyZWRpdCBDYXJkIElzc3VlZCBEYXRlIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKVGhlcmUgaXMgYSBzdGFibGUgaW5jcmVhc2Ugb2YgY3JlZGl0IGNhcmRzIHB1cmNoYXNlIGJldHdlZW4gMTk5NCBhbmQgMTk5Ni4gCnNpbmNlIDE5OTYsIHRoZSBncm93dGggd2FzIHJhcGlkIGFuZCByZWFjaGVkIHRoZSBwZWFrIGRlbnNpdHkgYmV0d2VlbiAxOTk4IGFuZCAxOTk5LCBpbmRpY2F0aW5nIHRoZSBwZXJpb2Qgb2YgaGlnaGVzdCBjcmVkaXQgY2FyZCBpc3N1YW5jZSBhY3Rpdml0eS4KCgoKSW4gdGhlIHdob2xlIGRhdGFzZXQsIDc0NyBjbGllbnRzIGhhdmUgYWxyZWFkeSBib3VnaHQgdGhlIGNyZWRpdCBjYXJkcywgMzc1MyBjbGllbnRzIGhhdmUgbm90IHB1cmNoYXNlZCBjcmVkaXQgY2FyZHMuIFRoZSByYXRpbyBvZiA1IG1lYW5zIHRoZXJlIGFyZSBhcHByb3hpbWF0ZWx5IDUgdGltZXMgIm5vbmJ1eWVycyIgYXMgImJ1eWVycyIgaW4gdGhlICdjYXJkX3R5cGUnIGNvbHVtbi4gRGF0YXNldCBpcyBzZXZlcmUgaW1iYWxhbmNlZC4gRm9yIHRoZSBmdXJ0aGVyIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxsaW5nLCBzYW1wbGluZyBtZXRob2RzIChlLmcuIG92ZXJzYW1wbGluZywgdW5kZXJzYW1wbGluZyAuLi4pIG9yIHN1aXRhYmxlIGV2YWx1YXRpb24gbWV0cmljcyBmb3IgaW1iYWxhbmNlZCBkYXRhIChlLmcuIGYxIHNjb3JlKSBtdXN0IGJlIGNvbnNpZGVyZWQuIAoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmJ1eWVyc190cmFucyA8LSBidXllcnMgJT4lIGxlZnRfam9pbih0cmFucyxieT0nYWNjb3VudF9pZCcpCmJ1eWVyc190cmFucyRtb250aHNfZGlmZmVyZW5jZSA8LSBhcy5pbnRlZ2VyKGRpZmZ0aW1lKCBidXllcnNfdHJhbnMkdHJhbnNfZGF0ZSxidXllcnNfdHJhbnMkY2FyZF9pc3N1ZWQsIHVuaXRzID0gImRheXMiKSAvIDMwLjQ0KSAtIDEKIyBrZWVwIHRoZSAyNCBtb250aHMgaGlzdG9yeSBiZWZvcmUgY3JlZGl0IGNhcmQgcHVyY2hhc2UsIGFzIG5lZWRlZCBmb3Igcm9sbHVwIHdpbmRvd3MuCmJ1eWVyc190cmFucyA8LSBidXllcnNfdHJhbnMgJT4lIGZpbHRlcihtb250aHNfZGlmZmVyZW5jZSA8IDAgJiBtb250aHNfZGlmZmVyZW5jZSA+PSAtMjQpCiMgY2FsY3VsYXRlIHRoZSB0b3RhbCB0cmFuc2FjdGlvbiBhbW91bnQgd2l0aGluIGVhY2ggbW9udGgsIGFuZCByb2xsdXAgd2luZG93cyB3aXRoIHNpemUgPSAxMiB0byBhdm9pZCB0aGUgc2Vhc29uYWwgZmFjdG9ycwp3aW5kb3dfc2l6ZSA8LSAxMgpidXllcnNfdHJhbnNfaGlzdCA8LSBidXllcnNfdHJhbnMgJT4lIGdyb3VwX2J5KGFjY291bnRfaWQsIG1vbnRoc19kaWZmZXJlbmNlKSAlPiUgc3VtbWFyaXNlKG1vbnRobHlfdHJhbnM9IHN1bShhbW91bnQpKSAlPiUgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lIGFycmFuZ2UoYWNjb3VudF9pZCwgbW9udGhzX2RpZmZlcmVuY2UpICU+JQogIG11dGF0ZShyb2xsX21vbnRobHlfdHJhbnMgPSBzYXBwbHkoc2VxX2Fsb25nKG1vbnRobHlfdHJhbnMpLCBmdW5jdGlvbihpKSBtZWFuKG1vbnRobHlfdHJhbnNbbWF4KDEsIGkgLSB3aW5kb3dfc2l6ZSArIDEpOmldKSkpICU+JQogIHVuZ3JvdXAoKSAlPiUgZmlsdGVyKG1vbnRoc19kaWZmZXJlbmNlID49IC0xMikKcHJpbnQoaGVhZChidXllcnNfdHJhbnNfaGlzdCkpCmBgYAoKCldlIGNvdWxkIHNlZSB0aGF0IGEgc2xvdyBncm93dGggb2YgdGhlIGF2ZXJhZ2UgbW9udGhseSB0cmFuc2FjdGlvbiBpbiB0aGUgZmlyc3QgMTEgbW9udGhzLiBJbiB0aGUgbGFzdCBtb250aCwgaXQgaW5jcmVhc2VkIHJhcGlkbHkgZnJvbSBhcm91bmQgNTgwMDAgdG8gMTE3MDAwLiBUbyBmdXJ0aGVyIHVuZGVyc3RhbmQgaWYgdGhlIHRlbmRlbmN5IGlzIGFmZmVjdGVkIGJ5IG91dGxpZXJzIGFuZCBza2V3ZWQgZGlzdHJpYnV0aW9uLCBJIHdpbGwgYWxzbyBjaGVjayB0aGUgbWVkaWFuLCB1cHBlci0gYW5kIGxvd2VyLXF1YXJ0aWxlcyB0ZW5kZW5jeS4KCgpgYGB7cn0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjYWxjdWxhdGUgYW5kIHBsb3Qgc3RhdGlzdGljcyAKY2FsY3VsYXRlX2FuZF9wbG90X3N0YXRzIDwtIGZ1bmN0aW9uKGRmLGNvbCx5X2xhYmVsLHRpdGxlLGEsYikgewogICMgQ2FsY3VsYXRlIG1lYW4sIG1lZGlhbiwgdXBwZXIgcXVhcnRpbGUsIGFuZCBsb3dlciBxdWFydGlsZQogIHN0YXRzIDwtIGRmICU+JQogICAgZ3JvdXBfYnkobW9udGhzX2RpZmZlcmVuY2UpICU+JQogICAgc3VtbWFyaXplKAogICAgICBtZWFuX2Ftb3VudCA9IG1lYW4oISFzeW0oY29sKSwgbmEucm0gPSBUUlVFKSwKICAgICAgbWVkaWFuX2Ftb3VudCA9IG1lZGlhbighIXN5bShjb2wpLCBuYS5ybSA9IFRSVUUpLAogICAgICB1cHBlcl9xdWFydGlsZV9hbW91bnQgPSBxdWFudGlsZSghIXN5bShjb2wpLCBwcm9icyA9IDAuNzUsIG5hLnJtID0gVFJVRSksCiAgICAgIGxvd2VyX3F1YXJ0aWxlX2Ftb3VudCA9IHF1YW50aWxlKCEhc3ltKGNvbCksIHByb2JzID0gMC4yNSwgbmEucm0gPSBUUlVFKSkKICAKICAjIENyZWF0ZSBhIGRhdGEgZnJhbWUgZm9yIGxhYmVscwogIGxhYmVsX2RmIDwtIGRhdGEuZnJhbWUoCiAgICBtb250aHNfZGlmZmVyZW5jZSA9IC00LAogICAgbGFiZWwgPSBjKCJNZWFuIiwiTWVkaWFuIiwgIlVwcGVyIFF1YXJ0aWxlIiwgIkxvd2VyIFF1YXJ0aWxlIiksCiAgICB5ID0gYygKICAgICAgc3RhdHMkbWVhbl9hbW91bnRbc3RhdHMkbW9udGhzX2RpZmZlcmVuY2UgPT0gLTJdWzFdLAogICAgICBzdGF0cyRtZWRpYW5fYW1vdW50W3N0YXRzJG1vbnRoc19kaWZmZXJlbmNlID09IC0yXVsxXSwKICAgICAgc3RhdHMkdXBwZXJfcXVhcnRpbGVfYW1vdW50W3N0YXRzJG1vbnRoc19kaWZmZXJlbmNlID09IC0yXVsxXSwKICAgICAgc3RhdHMkbG93ZXJfcXVhcnRpbGVfYW1vdW50W3N0YXRzJG1vbnRoc19kaWZmZXJlbmNlID09IC0yXVsxXSkpCiAgCiAgIyBDcmVhdGUgYSBsaW5lIHBsb3QKICBnZ3Bsb3Qoc3RhdHMsIGFlcyh4ID0gbW9udGhzX2RpZmZlcmVuY2UpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBtZWFuX2Ftb3VudCksIGNvbG9yID0gImJsYWNrIiwgbGluZXR5cGUgPSAic29saWQiKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBtZWRpYW5fYW1vdW50KSwgY29sb3IgPSAiYmx1ZSIsIGxpbmV0eXBlID0gInNvbGlkIikgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gdXBwZXJfcXVhcnRpbGVfYW1vdW50KSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAic29saWQiKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBsb3dlcl9xdWFydGlsZV9hbW91bnQpLCBjb2xvciA9ICJncmVlbiIsIGxpbmV0eXBlID0gInNvbGlkIikgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IC0xMjotMSkgKwogICAgbGFicygKICAgICAgeCA9ICJNb250aCIsCiAgICAgIHkgPSB5X2xhYmVsLAogICAgICB0aXRsZSA9IHRpdGxlKSArCiAgICBnZW9tX3RleHQoZGF0YSA9IGxhYmVsX2RmLCBhZXMobGFiZWwgPSBsYWJlbCwgeCA9IG1vbnRoc19kaWZmZXJlbmNlLCB5ID0geSksIHNpemUgPSAzLjUsIHZqdXN0ID0gLTAuNSwgaGp1c3QgPSAwLjUsIGNvbG9yID0gYygiYmxhY2siLCJibHVlIiwgInJlZCIsICJncmVlbiIpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoYSwgYikpICsgCiAgICB0aGVtZSgKICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwgICAjIEFkanVzdCB0aGUgYXhpcyBsYWJlbCB0ZXh0IHNpemUKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBoanVzdCA9IDAuNSkpfQoKCnBsb3QxID0gY2FsY3VsYXRlX2FuZF9wbG90X3N0YXRzKGJ1eWVyc190cmFuc19oaXN0LCAicm9sbF9tb250aGx5X3RyYW5zIiwgIk1vbnRobHkgVHJhbnNhY3Rpb24iLCAiQ3JlZGl0IENhcmQgQnV5ZXJzIiw4MDAwLDg1MDAwKQpwbG90MQpgYGAKCgpUaGUgbWVkaWFuLCB1cHBlciBxdWFydGlsZSwgbG93ZXIgcXVhcnRpbGUgbGluZXMgc2hvdyB2ZXJ5IHNpbWlsYXIgdGVuZGVuY3kgYXMgdGhlIGF2ZXJhZ2UgdHJhbnNhY3Rpb24gYW1vdW50LiBCdXQgdGhlIG1lZGlhbiBsaW5lIGlzIGNvbnNpc3RlbnRseSBsb3dlciB0aGFuIHRoZSBhdmVyYWdlIGxpbmUuIFRoaXMgbWVhbnMgdGhlIGRhdGEgaXMgbGVmdCAobmVnYXRpdmUpIHNrZXdlZCBkaXN0cmlidXRlZC4gSW4gdGhpcyBjYXNlLCBpdCBpcyBtb3JlIHJlYXNvbmFibGUgdG8gdXNlIG1lZGlhbiB0byBtZWFzdXJlIHRoZSB0ZW5kZW5jeS4KCgojIyMgMS40IERldGVybWluYXRpb24gb2Ygbm9uLWJ1eWVycyBmb3IgY29tcGFyaXNvbiAoaW5jbC4gcm9sbC11cCB3aW5kb3cpLgoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cm5vbmJ1eWVyc190cmFucyA8LSB0cmFucyAlPiUgaW5uZXJfam9pbihub25idXllcnMsIGJ5ID0gImFjY291bnRfaWQiKQpub25idXllcnNfdHJhbnMgPC0gbm9uYnV5ZXJzX3RyYW5zICU+JQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQogIGFycmFuZ2UoYWNjb3VudF9pZCwgZGVzYyh0cmFuc19kYXRlKSkgJT4lCiAgbXV0YXRlKGxhdGVzdF9kYXRlID0gZmlyc3QodHJhbnNfZGF0ZSkpICU+JQogIGZpbHRlcih0cmFuc19kYXRlID49IChsYXRlc3RfZGF0ZSAtIG1vbnRocygyNCkpKSAlPiUgdW5ncm91cCgpICU+JSAKICBtdXRhdGUoIG1vbnRoc19kaWZmZXJlbmNlID0gYXMuaW50ZWdlcihkaWZmdGltZSggdHJhbnNfZGF0ZSxsYXRlc3RfZGF0ZSwgdW5pdHMgPSAiZGF5cyIpIC8gMzAuNDQpIC0gMSkKCiMgY2FsY3VsYXRlIHRoZSB0b3RhbCB0cmFuc2FjdGlvbiBhbW91bnQgd2l0aGluIGVhY2ggbW9udGgKbm9uYnV5ZXJzX3RyYW5zX2hpc3QgPC0gbm9uYnV5ZXJzX3RyYW5zICU+JSBncm91cF9ieShhY2NvdW50X2lkLCBtb250aHNfZGlmZmVyZW5jZSkgJT4lIHN1bW1hcmlzZShtb250aGx5X3RyYW5zID0gc3VtKGFtb3VudCkpICU+JQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQogIGFycmFuZ2UoYWNjb3VudF9pZCwgbW9udGhzX2RpZmZlcmVuY2UpICU+JQogIG11dGF0ZShyb2xsX21vbnRobHlfdHJhbnMgPSBzYXBwbHkoc2VxX2Fsb25nKG1vbnRobHlfdHJhbnMpLCBmdW5jdGlvbihpKSBtZWFuKG1vbnRobHlfdHJhbnNbbWF4KDEsIGkgLSB3aW5kb3dfc2l6ZSArIDEpOmldKSkpICU+JQogIHVuZ3JvdXAoKSAlPiUgZmlsdGVyKG1vbnRoc19kaWZmZXJlbmNlID49IC0xMikKcHJpbnQoaGVhZChub25idXllcnNfdHJhbnNfaGlzdCkpCmBgYAoKYGBge3J9CnBsb3QyID0gY2FsY3VsYXRlX2FuZF9wbG90X3N0YXRzKG5vbmJ1eWVyc190cmFuc19oaXN0LCAicm9sbF9tb250aGx5X3RyYW5zIiwgIiAiLCAiIENyZWRpdCBDYXJkIE5vbi1CdXllcnMiLDgwMDAsODUwMDApCmNvbW1vbl90aXRsZSA8LSBnZ2RyYXcoKSArIGRyYXdfbGFiZWwoIjEyIE1vbnRocyBSb2xsdXAgVHJhbnNhY3Rpb24gSGlzdG9yeSIsIGZvbnRmYWNlPSdib2xkJywgc2l6ZSA9IDE0KQpib3R0b21fcm93IDwtIHBsb3RfZ3JpZChwbG90MSwgcGxvdDIsIG5jb2wgPSAyKQpwbG90X2dyaWQoY29tbW9uX3RpdGxlLCBib3R0b21fcm93LCBucm93ID0gMiwgcmVsX2hlaWdodHMgPSBjKDAuMiwgMSkpCmBgYAoKCgpBbmFseXNpcyBvZiBub24tYnV5ZXJzOiBBbGwgZm91ciBzdGF0aXN0aWNzIGxpbmVzIGhhdmUgdmVyeSBzaW1pbGFyIHRlbmRlbmN5LCB3aGVyZSBtZWRpYW4gaXMgY29uc3RhbnRseSBsb3dlciB0aGFuIG1lYW4sIHRoaXMgaW5kaWNhdGVzIHRoZSBkYXRhIGlzIG5lZ2F0aXZlIChsZWZ0KSBza2V3ZWQgZGlzdHJpYnV0ZWQuIEluIHN1Y2ggY2FzZSwgbWVkaWFuIGlzIG1vcmUgcm9idXN0IHRoYW4gbWVhbiB0byByZXByZXNlbnQgdGhlIGRhdGEuCm5vbi1idXllcnMgdHJhbnNhY3Rpb24gdGVuZGVuY3kgb2YgdGhlIGxhc3QgMTIgbW9udGhzIGlzIHJlbGF0aXZlbHkgc3RhYmxlIHdpdGggYSBwZWFrIGF0IHNldmVudGggbW9udGguIFRoZSBhbW91bnQgb2YgMTJ0aCBtb250aCBhbmQgbGFzdCBtb250aCBhcmUgb2J2aW91c2x5IGhpZ2hlciB0aGFuIHRoZSBvdGhlciBtb250aHMuIAoKCgpTdW1tYXJ5IGJhc2VkIG9uIHRoZSBjb21wYXJpc29uIG9mIHRyYW5zYWN0aW9uIHJvbGx1cCB3aW5kb3dzIEJ1eWVycyBWUyBub24tQnV5ZXJzOiAKLSB0aGUgbW9udGhseSB0cmFuc2FjdGlvbiBhbW91bnQgb2YgYnV5ZXJzIChtZWRpYW46IGFyb3VuZCA0MCwwMDAgLSA5NSwwMDApIGlzIG11Y2ggbGFyZ2VyIHRoYW4gdGhlIG5vbmJ1eWVycyAobWVkaWFuOiBhcm91bmQgMTgsMDAwIC0gMjksMDAwKS4KLSBidXllcnMgYW5kIG5vbi1idXllcnMgaGF2ZSBkaWZmZXJlbnRseSB0ZW5kZW5jeSBvdmVyIHRoZSBsYXN0IDEyIG1vbnRocyBiZWZvcmUgY3JlZGl0IGNhcmQgcHVyY2hhc2UuIFRoaXMgbWVhbnMsIHRyYW5zYWN0aW9uIGFtb3VudCByZWxhdGVkIGZlYXR1cmVzIG1heSBiZSBpbXBvcnRhbnQgdG8gY2xhc3NpZnkgY3JlZGl0IGNhcmQgYnV5ZXJzIGFuZCBub24tYnV5ZXJzLiBGb3IgbWFjaGluZSBsZWFybmluZyBtb2RlbGluZywgSSB3aWxsIGluY2x1ZGUgY29tcHJlaGVuc2l2ZSBjbGllbnQtc3BlY2lmaWMgc3RhdGlzdGljYWwgZmVhdHVyZSBlbmdpbmVlcmluZyBmb3IgdGhlIHRyYW5zYWN0aW9uIGFtb3VudC4KLSBib3RoIGRhdGFzZXQgYXJlIGltYmFsYW5jZWQsIGJhbGFuY2luZyBtZXRob2RzIG9yIGV2YWx1YXRpb24gbWV0cmljcyBmb3IgaW1iYWxhbmNlZCBkYXRhIG11c3QgYmUgY29uc2lkZXJlZC4KCgoKIyMjIDEuNSBDb25zdHJ1Y3RpbmcgdGhlIGFzc2V0cyBhbmQgdHVybm92ZXJzIGluIHRoZSByb2xsdXAgd2luZG93IGJhc2VkIG9uIHRoZSB0cmFuc2FjdGlvbiBoaXN0b3J5LgoKQmFzZWQgb24gdGhlIGRhdGEgd2UgaGF2ZSwgYXNzZXQgY291bGQgYmUgY2FsY3VsYXRlZCBsaWtlIHRoaXM6IGFzc2V0ID0gYmFsYW5jZSAtIGxvYW4gKGlmIHRoZSBhY2NvdW50IGhhcyBsb2FuKS4gT2YgY291cnNlLCBJIHNob3VsZCBjb25zaWRlciBob3cgbXVjaCBsb2FuIGhhcyBhbHJlYWR5IGJlZW4gcGFpZCBmb3IgdGhlIHRpbWUgcG9pbnQgb2YgYXNzZXQgY2FsY3VsYXRpb24uIFRoZSAiVVZFUiIga19zeW1ib2wgaW4gdHJhbnNhY3Rpb24gc3RhbmRzIGZvciBsb2FuIHBheW1lbnQuIFdpdGggdGhpcyB3ZSBjb3VsZCBjYWxjdWxhdGUgdGhlIHJlbWFpbmluZyBsb2FuIGZvciBlYWNoIG9ic2VydmF0aW9ucyBpbiB0cmFuc2FjdGlvbiBkYXRhLgoKICAKYGBge3J9CnRyYW5zX2xvYW4gPC0gdHJhbnMgJT4lIGxlZnRfam9pbihsb2FuLCBieSA9ICJhY2NvdW50X2lkIiwgc3VmZml4ID0gYygiX3RyYW5zIiwiX2xvYW4iKSkgCmNvbG5hbWVzKHRyYW5zX2xvYW4pWzRdIDwtICJ0cmFuc19hbW91bnQiCgojIHNwbGl0IHRoZSB0cmFuc2FjdGlvbiBsb2FuIGRhdGEgaW50byB0d28gcGFydHMKIyBwYXJ0IDE6IHRoZSBhY2NvdW50IGhhZCBsb2FuIGZyb20gdGhlIGJhbms7IGhlcmUgYXNzZXQgPSBiYWxhbmNlIC0gcmVtYWluaW5nIGxvYW4KdHJhbnNfbG9hbl8xIDwtIHRyYW5zX2xvYW4gJT4lCiAgZmlsdGVyKHRyYW5zX2RhdGUgPj0gbG9hbl9kYXRlKSAlPiUKICBhcnJhbmdlKGFjY291bnRfaWQsIHRyYW5zX2RhdGUpICU+JQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JSBtdXRhdGUoCiAgICBwYWlkX2xvYW4gPSBpZmVsc2UoZ3JlcGwoIlVWRVIiLCB0cmFuc19kZXRhaWwsIGlnbm9yZS5jYXNlID0gVFJVRSksIHRyYW5zX2Ftb3VudCwgMCksICMgYW1vdW50IG9mIHBhaWQgbG9hbiBpbiBlYWNoIHRyYW5zYWN0aW9uCiAgICBhY2N1bV9wYWlkX2xvYW4gPSBjdW1zdW0ocGFpZF9sb2FuKSwgIyBhY2N1bXVsYXRlZCBwYWlkIGxvYW4KICAgIHJlbWFpbmluZ19sb2FuID0gbG9hbl9hbW91bnQgLSBhY2N1bV9wYWlkX2xvYW4sICMgcmVtYWluaW5nIGxvYW4KICAgIHJlbWFpbmluZ19sb2FuID0gaWZlbHNlKHJlbWFpbmluZ19sb2FuIDwgMCwgMCwgcmVtYWluaW5nX2xvYW4pLCAjIHJlcGxhY2UgbmVnYXRpdmUgcmVtYWluaW5nIGxvYW4gYXMgMCwgdGhpcyBtZWFucyBsb2FuIGlzIHRvdGFsbHkgcGFpZC4KICAgIGFzc2V0ID0gYmFsYW5jZSAtIHJlbWFpbmluZ19sb2FuKSAlPiUgdW5ncm91cCgpICU+JSBzZWxlY3QoLWMobG9hbl9pZCxwYWlkX2xvYW4sYWNjdW1fcGFpZF9sb2FuLHJlbWFpbmluZ19sb2FuKSkgIyBhc3NldCAKIyBwYXJ0IDI6IHRoZSBhY2NvdW50IHdoaWNoIGhhcyBuZXZlciBiZWVuIGlzc3VlZCBhIGxvYW4sIGFuZCB0aGUgYWNjb3VudCBiZWZvcmUgbG9hbiBpcyBpc3N1ZWQuCnRyYW5zX2xvYW5fMiA8LSB0cmFuc19sb2FuICU+JSBmaWx0ZXIodHJhbnNfZGF0ZSA8IGxvYW5fZGF0ZSB8IGlzLm5hKGxvYW5fZGF0ZSkpICU+JSBtdXRhdGUoYXNzZXQgPSBiYWxhbmNlKSAlPiUgc2VsZWN0KC1sb2FuX2lkKQp0cmFuc19sb2FuIDwtIHJiaW5kKHRyYW5zX2xvYW5fMSwgdHJhbnNfbG9hbl8yKSAKcHJpbnQoaGVhZCh0cmFuc19sb2FuLDUpKQpgYGAKCgpOb3csIEkgd2lsbCBqb2luIHRoZSBhc3NldCBkYXRhIHRvIHRoZSB0cmFuc2FjdGlvbiBkYXRhIG9mIGJ1eWVycyBhbmQgbm9uLWJ1eWVycyAKCmBgYHtyfQpidXllcnNfdHJhbnMgPC0gYnV5ZXJzX3RyYW5zICU+JSBsZWZ0X2pvaW4odHJhbnNfbG9hblssIGMoInRyYW5zX2lkIiwgImFzc2V0IildLCBieSA9ICd0cmFuc19pZCcpCm5vbmJ1eWVyc190cmFucyA8LSBub25idXllcnNfdHJhbnMgJT4lIGxlZnRfam9pbih0cmFuc19sb2FuWywgYygidHJhbnNfaWQiLCAiYXNzZXQiKV0sIGJ5ID0gJ3RyYW5zX2lkJykKcHJpbnQoaGVhZChidXllcnNfdHJhbnMsNSkpCmBgYAoKY29uc3RydWN0IHRoZSBhc3NldHMgdXNpbmcgMTItbW9udGggcm9sbHVwIHdpbmRvdwpBZnRlciBlYWNoIHRyYW5zYWN0aW9uLCB0aGUgYXNzZXQgb2YgYW4gYWNjb3V0IGdldCB1cGRhdGVkLCB0aGlzIG1lYW5zIGVhY2ggYWNjb3VudCBjb250YWlucyBtYW55IGFzc2V0IHJlY29yZHMuIEluIHRoaXMgY2FzZSBpdCBtYWtlcyBtb3JlIHNlbnNlIHRvIHVzZSBhdmVyYWdlIGFzc2V0IHdpdGhpbiBhIG1vbnRoIHRvIHJlcHJlc2VudCB0aGUgbW9udGhseSBhc3NldC4KCgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KYnV5ZXJzX2Fzc2V0X2hpc3QgPC0gYnV5ZXJzX3RyYW5zICU+JSBncm91cF9ieShhY2NvdW50X2lkLCBtb250aHNfZGlmZmVyZW5jZSkgJT4lIHN1bW1hcmlzZShtb250aGx5X2Fzc2V0PSBtZWFuKGFtb3VudCkpICU+JSBncm91cF9ieShhY2NvdW50X2lkKSAlPiUgYXJyYW5nZShhY2NvdW50X2lkLCBtb250aHNfZGlmZmVyZW5jZSkgJT4lCiAgbXV0YXRlKHJvbGxfbW9udGhseV9hc3NldCA9IHNhcHBseShzZXFfYWxvbmcobW9udGhseV9hc3NldCksIGZ1bmN0aW9uKGkpIG1lYW4obW9udGhseV9hc3NldFttYXgoMSwgaSAtIHdpbmRvd19zaXplICsgMSk6aV0pKSkgJT4lCiAgdW5ncm91cCgpICU+JSBmaWx0ZXIobW9udGhzX2RpZmZlcmVuY2UgPj0gLTEyKQojIGRvIHRoZSBzYW1lIGZvciB0aGUgbm9uYnV5ZXJzCm5vbmJ1eWVyc19hc3NldF9oaXN0IDwtIG5vbmJ1eWVyc190cmFucyAlPiUgZ3JvdXBfYnkoYWNjb3VudF9pZCwgbW9udGhzX2RpZmZlcmVuY2UpICU+JSBzdW1tYXJpc2UobW9udGhseV9hc3NldD0gbWVhbihhbW91bnQpKSAlPiUgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lIGFycmFuZ2UoYWNjb3VudF9pZCwgbW9udGhzX2RpZmZlcmVuY2UpICU+JQogIG11dGF0ZShyb2xsX21vbnRobHlfYXNzZXQgPSBzYXBwbHkoc2VxX2Fsb25nKG1vbnRobHlfYXNzZXQpLCBmdW5jdGlvbihpKSBtZWFuKG1vbnRobHlfYXNzZXRbbWF4KDEsIGkgLSB3aW5kb3dfc2l6ZSArIDEpOmldKSkpICU+JQogIHVuZ3JvdXAoKSAlPiUgZmlsdGVyKG1vbnRoc19kaWZmZXJlbmNlID49IC0xMikKcHJpbnQoaGVhZChidXllcnNfYXNzZXRfaGlzdCkpCmBgYAoKYGBge3J9CnBsb3QzID0gY2FsY3VsYXRlX2FuZF9wbG90X3N0YXRzKGJ1eWVyc19hc3NldF9oaXN0LCAicm9sbF9tb250aGx5X2Fzc2V0IiwgIk1vbnRobHkgQXNzZXQiLCAiQ3JlZGl0IENhcmQgQnV5ZXJzIiwyMDAwLDEyMDAwKQpwbG90NCA9IGNhbGN1bGF0ZV9hbmRfcGxvdF9zdGF0cyhub25idXllcnNfYXNzZXRfaGlzdCwgInJvbGxfbW9udGhseV9hc3NldCIsICIgIiwgIkNyZWRpdCBDYXJkIE5vbi1CdXllcnMiLDIwMDAsMTIwMDApCgpjb21tb25fdGl0bGUgPC0gZ2dkcmF3KCkgKyBkcmF3X2xhYmVsKCIxMiBNb250aHMgUm9sbHVwIEFzc2V0IEhpc3RvcnkiLCBmb250ZmFjZT0nYm9sZCcsIHNpemUgPSAxNCkKYm90dG9tX3JvdyA8LSBwbG90X2dyaWQocGxvdDMsIHBsb3Q0LCBuY29sID0gMikKcGxvdF9ncmlkKGNvbW1vbl90aXRsZSwgYm90dG9tX3JvdywgbnJvdyA9IDIsIHJlbF9oZWlnaHRzID0gYygwLjIsIDEpKQpgYGAKCgpDb21wYXJlIEFzc2V0IHJvbGx1cCBoaXN0b3J5IG9mIGJ1eWVycyBhbmQgbm9uLWJ1eWVyczoKLSBUaGUgYnV5ZXJzIGhhdmUgc2lnbmlmaWNhbnQgaGlnaGVyIGFzc2V0IChtZWRpYW46IGFyb3VuZCA4MzAwKSB0aGFuIHRoZSBub25idXllcnMgKG1lZGlhbjogNDkwMCAtIDU1MDApCi0gVGhlIGFzc2V0IG9mIGJ1eWVycyBpcyBzbGlnaHRseSBpbmNyZWFzZWQgb3ZlciB0aGUgMTIgbW9udGhzLCBpbiBjb250cmFzdCwgdGhlIG5vbmJ1eWVycycgYXNzZXQgYW1vdW50IGlzIGRlY3JlYXNpbmcgb3ZlcnRpbWUuCi0gVGhpcyBtZWFucywgdGhlIGFzc2V0IChzbG9wZSwgbWVhbiwgbWVkaWFuLCBxdWFydGlsZXMpIGNvdWxkIGJlIGFuIGltcG9ydGFudCBmZWF0dXJlIGZvciBpZGVudGlmeWluZyBidXllcnMgYW5kIG5vbmJ1eWVycy4gCgojIyMgMS42IERlcml2aW5nIGN1c3RvbWVyLXNwZWNpZmljLCBzdGF0aXN0aWNhbCBrZXkgZmlndXJlcyBmb3IgYXNzZXRzIGFuZCBzYWxlcyBpbiB0aGUgcm9sbHVwIHdpbmRvdyB1c2luZyBmdW5jdGlvbnMuCgpzdGF0aXN0aWNhbCBmZWF0dXJlIGVuZ2luZWVyaW5nCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KE1BU1MpCnN0YXRfZmVhdHVyZXMgPC0gZnVuY3Rpb24oZGYsIGNvbCwgc3VmZml4KSB7CiMgZGVmaW5lIGEgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIG1lYW4sIG1lZGlhbiwgdXBwZXIgcXVhcnRpbGUsIGxvd2VyIHF1YXJ0aWxlLCBtaW4sIG1heCwKICBzdGF0cyA8LSBkZiAlPiUKICAgIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQogICAgc3VtbWFyaXplKAogICAgICAhIXBhc3RlMCgibWVhbl8iLCBzdWZmaXgpIDo9IG1lYW4oISFzeW0oY29sKSwgbmEucm0gPSBUUlVFKSwKICAgICAgISFwYXN0ZTAoIm1lZGlhbl8iLCBzdWZmaXgpIDo9IG1lZGlhbighIXN5bShjb2wpLCBuYS5ybSA9IFRSVUUpLAogICAgICAhIXBhc3RlMCgidXBwZXJfcXVhcnRpbGVfIiwgc3VmZml4KSA6PSBxdWFudGlsZSghIXN5bShjb2wpLCBwcm9icyA9IDAuNzUsIG5hLnJtID0gVFJVRSksCiAgICAgICEhcGFzdGUwKCJsb3dlcl9xdWFydGlsZV8iLCBzdWZmaXgpIDo9IHF1YW50aWxlKCEhc3ltKGNvbCksIHByb2JzID0gMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgICAgISFwYXN0ZTAoIm1pbl8iLCBzdWZmaXgpIDo9IG1pbighIXN5bShjb2wpLCBuYS5ybSA9IFRSVUUpLAogICAgICAhIXBhc3RlMCgibWF4XyIsIHN1ZmZpeCkgOj0gbWF4KCEhc3ltKGNvbCksIG5hLnJtID0gVFJVRSksCiAgICAgICEhcGFzdGUwKCJ2YXJfIiwgc3VmZml4KSA6PSB2YXIoISFzeW0oY29sKSwgbmEucm0gPSBUUlVFKSwKICAgICAgISFwYXN0ZTAoInN0ZF8iLCBzdWZmaXgpIDo9IHNkKCEhc3ltKGNvbCksIG5hLnJtID0gVFJVRSksCiAgICAgICEhcGFzdGUwKCJuX3Bvc19jaGFuZ2VzXyIsIHN1ZmZpeCkgOj0gc3VtKGRpZmYoISFzeW0oY29sKSkgPiAwKSwKICAgICAgISFwYXN0ZTAoIm5fbmVnX2NoYW5nZXNfIiwgc3VmZml4KSA6PSBzdW0oZGlmZighIXN5bShjb2wpKSA8IDApLAogICAgICAhIXBhc3RlMCgibWFkXyIsIHN1ZmZpeCkgOj0gbWFkKCEhc3ltKGNvbCksIG5hLnJtID0gVFJVRSksCiAgICAgICEhcGFzdGUwKCJzYWRfIiwgc3VmZml4KSA6PSBzdW0oYWJzKGRpZmYoISFzeW0oY29sKSkpKSwgIyBzdW0gb2YgYWJzb2x1dGUgZGlmZmVyZW5jZXMKICAgICAgISFwYXN0ZTAoIm5fYWJvdmVfbWVhbl8iLCBzdWZmaXgpIDo9IHN1bSghIXN5bShjb2wpID4gbWVhbighIXN5bShjb2wpLCBuYS5ybSA9IFRSVUUpKSwKICAgICAgISFwYXN0ZTAoIm5fYmVsb3dfbWVhbl8iLCBzdWZmaXgpIDo9IHN1bSghIXN5bShjb2wpIDwgbWVhbighIXN5bShjb2wpLCBuYS5ybSA9IFRSVUUpKSwKICAgICAgISFwYXN0ZTAoInNsb3BlXyIsIHN1ZmZpeCkgOj0gY29lZihsbSghIXN5bShjb2wpIH4gc2VxX2Fsb25nKCEhc3ltKGNvbCkpKSlbMl0sICMgY29lZmZpY2llbnQgb2YgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwKICAgICAgISFwYXN0ZTAoInJvYnVzdF90cmVuZF8iLCBzdWZmaXgpIDo9IGNvZWYocmxtKCEhc3ltKGNvbCkgfiBzZXFfYWxvbmcoISFzeW0oY29sKSkpKVsyXSkgIyBjb2VmZmljaWVudCBvZiByb2J1c3QgbGluZWFyIHJlZ3Jlc3Npb24KICAgCiAgcmV0dXJuKHN0YXRzKX0KCiMgc3RhdGlzdGljYWwgcm9sbHVwIHRyYW5zYWN0aW9uIG9mIGJ1eWVycyBhbmQgbm9uLWJ1eWVycwpidXllcnNfdHJhbnNfc3RhdHMgPSBzdGF0X2ZlYXR1cmVzKGJ1eWVyc190cmFuc19oaXN0LCJyb2xsX21vbnRobHlfdHJhbnMiLCJ0cmFucyIpCm5vbmJ1eWVyc190cmFuc19zdGF0cyA9IHN0YXRfZmVhdHVyZXMobm9uYnV5ZXJzX3RyYW5zX2hpc3QsInJvbGxfbW9udGhseV90cmFucyIsInRyYW5zIikKdHJhbnNfc3RhdHMgPC0gcmJpbmQoYnV5ZXJzX3RyYW5zX3N0YXRzLCBub25idXllcnNfdHJhbnNfc3RhdHMpCiMgc3RhdGlzdGljYWwgcm9sbHVwIGFzc2V0IG9mIGJ1eWVycyBhbmQgbm9uLWJ1eWVycwpidXllcnNfYXNzZXRfc3RhdHMgPSBzdGF0X2ZlYXR1cmVzKGJ1eWVyc19hc3NldF9oaXN0LCJyb2xsX21vbnRobHlfYXNzZXQiLCJhc3NldCIpCm5vbmJ1eWVyc19hc3NldF9zdGF0cyA9IHN0YXRfZmVhdHVyZXMobm9uYnV5ZXJzX2Fzc2V0X2hpc3QsInJvbGxfbW9udGhseV9hc3NldCIsImFzc2V0IikKYXNzZXRfc3RhdHMgPC0gcmJpbmQoYnV5ZXJzX2Fzc2V0X3N0YXRzLCBub25idXllcnNfYXNzZXRfc3RhdHMpCnByaW50KGhlYWQodHJhbnNfc3RhdHMsMykpCmBgYAoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgZGV0YWNoIGxpYnJhcnkgTUFTUywgYXMgaXQgZ2VuZXJhdGVzIGNvbmZsaWN0cyB3aXRoIG90aGVyIGxpYnJhcmllcy4KZGV0YWNoKCJwYWNrYWdlOk1BU1MiLCB1bmxvYWQgPSBUUlVFKSAKcHJpbnQoaGVhZChhc3NldF9zdGF0cywzKSkKYGBgCgpgYGB7cn0KIyBqb2luIHRoZSBzdGF0aXN0aWNhbCBmZWF0dXJlcyB0byB0aGUgb3RoZXIgZGF0YSBieSBhY2NvdW50X2lkCmRmIDwtIGRmMSAlPiUgbGVmdF9qb2luKHRyYW5zX3N0YXRzLCBieSA9ICdhY2NvdW50X2lkJykgJT4lIGxlZnRfam9pbihhc3NldF9zdGF0cywgYnkgPSAnYWNjb3VudF9pZCcpCnByaW50KGhlYWQoZGYsMykpCmBgYApgYGB7cn0KIyBjaGVjayBhZ2FpbiBpZiBvbmUgYWNjb3VudF9pZCBoYXMgb25seSBvbmUgZGF0YSBwb2ludC4KZGZfY291bnQgPC0gZGYgJT4lIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JSBzdW1tYXJpemUocm93X2NvdW50ID0gbigpKQppZiAoYW55KGRmX2NvdW50JHJvd19jb3VudCA+IDEpKSB7cHJpbnQoIlNvbWUgYWNjb3VudF9pZCB2YWx1ZXMgaGF2ZSBtb3JlIHRoYW4gb25lIHJvdy4iKX0gZWxzZSB7cHJpbnQoIkVhY2ggYWNjb3VudF9pZCBoYXMgb25seSBvbmUgcm93LiIpfQpgYGAKYGBge3J9CmNhdCgiTnVtYmVyIG9mIFJvd3M6IiwgZGltKGRmKVsxXSwgIlxuIikKY2F0KCJOdW1iZXIgb2YgQ29sdW1uczoiLCBkaW0oZGYpWzJdLCAiXG4iKQpjYXQoIlRvdGFsIE51bWJlciBvZiBOQXMgaW4gdGhlIERhdGFGcmFtZToiLCBzdW0oY29sU3Vtcyhpcy5uYShkZikpKSwgIlxuIikKYGBgCgoKIyMjIDEuNyBDbGVhbmluZyB1cCB1bm5lY2Vzc2FyeSBpbmZvcm1hdGlvbiBhbmQgY2hlY2tpbmcgdGhlIHN0cnVjdHVyZSBvZiB0aGUgbW9kZWxpbmcgZGF0YSAKCi0gTG9hbiBpbmZvcm1hdGlvbnMgYXJlIG9ubHkgYXZhaWxhYmxlIGZvciB0aGUgYWNjb3VudHMgd2hpY2ggaGF2ZSBiZWVuIGlzc3VlZCBsb2FuLiBGb3IgdGhlIGFjY291bnRzIHdpdGhvdXQgbG9hbiwgTkFzIGluIHRoZXNlIHZhcmlhbGJlcyBjYW4gbm90IGJlIGZpbGxlZC4gU28gSSB3aWxsIGdlbmVyYXRlIGEgbmV3IGNvbHVtbiAnbG9hbl9pc3N1YW5jZScgdG8gcmVwcmVzZW50IGlmIGFuIGFjY291bnQgaGFzIGJlZW4gaXNzdWVkIGxvYW4sIGFmdGVyd2FyZHMgZHJvcCB0aGUgb3RoZXIgaXJyZWxhdmFudCBsb2FuIHZhcmlhYmxlcy4KLSBTaW1pbGFybHksIGNhcmQgcmVsZXZhbnQgaW5mb3JtYXRpb24gaXMgb25seSBhdmFpbGFibGUgZm9yIGNhcmQgYnV5ZXJzLCBzbyBJIHdpbGwgYWxzbyBkcm9wIGNvbHVtbnMgY2FyZF9pc3N1ZWQsIGNhcmRfYWdlLiBUaGUgY29udGVudCBvZiBjYXJkX3R5cGUgc2hvdWxkIGJlIGNoYW5nZWQgdG8gYnV5ZXJzIChjbGFzc2ljLCBnb2xkKSBhbmQgbm9uYnV5ZXJzIChOQSkuCi0gZHJvcCBhY2NvdW50X2lkIG9yIGNsaWVudF9pZCwgYmVjYXVzZSBjbGllbnRfaWQgYW5kIGFjY291bnRfaWQgYXJlIG9uZS10by1vbmUgcmVsYXRlZCwgY2xpZW50X2lkIGlzIGltcG9ydGFudCBmb3IgaWRlbnRpZnlpbmcgdGhlIHRvcC1OIGltcG9ydGFudCBjbGllbnRzIGluIGxhdGVyIHBhcnQuIFNvIEkgd2lsbCBkcm9wIGFjY291bnRfaWQuCi0gYWRkIHRoZSBhZ2VzIG9mIGNsaWVudHMgaW4gMTk5OS0xLTEgYW5kIGFnZXMgb2YgYWNjb3VudHMgKGFzIHRoZSB0YXNrIHdhcyBlc3RhYmxpc2hlZCksIGFmdGVyd2FyZHMgZHJvcCBiaXJ0aGRheSwgYWNjb3VudF9kYXRlLiAKYGBge3J9CmRmIDwtIGRmICU+JQogIG11dGF0ZSgKICAgIGxvYW5faXNzdWFuY2UgPSBpZmVsc2UoaXMubmEobG9hbl9pZCksICJObyIsICJZZXMiKSwKICAgIGNhcmRfdHlwZSA9IGlmZWxzZShpcy5uYShjYXJkX3R5cGUpLCAibm9uYnV5ZXJzIiwgImJ1eWVycyIpLAogICAgY2xpZW50X2FnZSA9IGFzLm51bWVyaWMoZGlmZnRpbWUoJzE5OTktMDEtMDEnLCBiaXJ0aGRheSwgdW5pdHMgPSAiZGF5cyIpKSAvIDM2NS4wLAogICAgYWNjb3VudF9hZ2UgPSBhcy5udW1lcmljKGRpZmZ0aW1lKCcxOTk5LTAxLTAxJywgYWNjb3VudF9kYXRlLCB1bml0cyA9ICJkYXlzIikpIC8gMzY1LjAKICApICU+JQogIGRwbHlyOjpzZWxlY3QoLWMoYWNjb3VudF9pZCwgbG9hbl9pZCwgbG9hbl9hbW91bnQsIGxvYW5fZHVyYXRpb24sIGxvYW5fcGF5bWVudHMsIGxvYW5fc3RhdHVzLCBsb2FuX2RhdGUsIGxvYW5fYWdlLCBjYXJkX2lzc3VlZCwgY2FyZF9hZ2UsIGJpcnRoZGF5LCBhY2NvdW50X2RhdGUpKQpkZiA8LSBkZlssIDI6bmNvbChkZildCgojIFJvdW5kICdjbGllbnRfYWdlJyBhbmQgJ2FjY291bnRfYWdlJyB0byAxIGRlY2ltYWwgcGxhY2UKZGYkY2xpZW50X2FnZSA8LSByb3VuZChkZiRjbGllbnRfYWdlLCAxKQpkZiRhY2NvdW50X2FnZSA8LSByb3VuZChkZiRhY2NvdW50X2FnZSwgMSkKCiMgQ291bnQgTkFzIG9mIGluY29tcGxldGUgY29sdW1ucwpjb2xfd2l0aF9uYSA8LSBkZlssIGNvbFN1bXMoaXMubmEoZGYpKSAhPSAwXQpjb2xTdW1zKGlzLm5hKGNvbF93aXRoX25hKSkKYGBgClRoZXNlIDYgY29sdW1ucyBoYXZlIG51bWVyaWMgZGF0YS4gVGhleSByZXByZXNlbnQgdGhlIG9yZGVyIGFtb3VudCBvZiBhY2NvdW50cy4gCk5BcyBpbmRpY2F0ZSB0aGUgb3JkZXIgYW1vdW50ID0gMC4KU28gaGVyZSBJIHdpbGwgcmVwbGFjZSB0aGUgTkFzIHdpdGggMAoKYGBge3J9CmRmW2lzLm5hKGRmKV0gPC0gMApjYXQoICJUaGVyZSBhcmUiLCBzdW0oaXMubmEoZGYpKSwgIk5BcyBpbiBkYXRhIGRmLlxuIikKY2F0KCJOdW1iZXIgb2YgUm93czoiLCBkaW0oZGYpWzFdLCAiXG4iKQpjYXQoIk51bWJlciBvZiBDb2x1bW5zOiIsIGRpbShkZilbMl0sICJcbiIsIkRhdGEgc2l6ZSBpcyBtb2RlcmF0ZS4iKQpgYGAKYGBge3J9CnByaW50KGhlYWQoZGYpKQpgYGAKYGBge3J9CmNvbG5hbWVzKGRmKQpgYGAKCkluIHN1bW1hcnksIGF2YWlsYWJsZSB2YXJpYWJsZXMgY291bGQgYmUgY2F0ZWdvcmllZCBhcyBmb2xsb3dpbmc6Ci0gSW5mb3JtYXRpb24gb2YgYWNjb3VudCBvd25lcjogZ2VuZGVyLCBiaXJ0aGRheSwgYWdlLCByZXNpZGVudGlhbCBkaXN0cmljdAotIEluZm8gb2YgYWNjb3VudDogc3RhcnQgZGF0ZSwgYWNjb3VudCBhZ2UsIG51bWJlciBvZiBjbGllbnRzIHVuZGVyIHRoZSBhY2NvdW50X2lkCi0gZGV0YWlsZWQgaW5mb3JtYXRpb24gb2YgcmVzaWRlbnRpYWwgZGlzdHJpY3QKLSBwZXJtYW5hbnQgb3JkZXIgYW1vdW50IGJ5IG9yZGVyIHR5cGVzCi0gdHJhbnNhY3Rpb246IDEyLW1vbnRoLXJvbGx1cCBzdGF0aXN0aWNhbCB2YXJpYWJsZXMgCi0gYXNzZXQ6IDEyLW1vbnRoLXJvbGx1cCBzdGF0aXN0aWNhbCB2YXJpYWJsZXMgCgojIyMgMS44IFBhcnRpdGlvbiB0aGUgZGF0YSBpbnRvIHRyYWluaW5nLCB2YWxpZGF0aW9uIGFuZCB0ZXN0IGRhdGEuCkkgd2lsbCBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIHRocmVlIHBhcnRzIHRvIHByZXZlbnQgZGF0YSBsZWFrYWdlOiAKICAtIFRyYWluaW5nIHNldDogdXNlZCB0byB0cmFpbiB0aGUgcHJlZGljdGl2ZSBtb2RlbC4KICAtIFZhbGlkYXRpb24gc2V0OiB1c2VkIGZvciBtb2RlbCBmaW5lLXR1bmluZyBhbmQgaHlwZXJwYXJhbWV0ZXIgc2VsZWN0aW9uLiAKICAtIFRlc3Qgc2V0OiB1c2VkIHRvIGV2YWx1YXRlIHRoZSBmaW5hbCBwZXJmb3JtYW5jZS4gClRvIHByZXZlbnQgdGhlIGRlcGVuZGVuY3kgb24gaG93IHRyYWluIHRlc3QgYXJlIHNwbGl0dGVkLCBJIHdpbGwgdXNlIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBpbiB0aGUgbW9kZWxpbmcgcGFydCB0byBlbmhhbmNlIHRoZSByb2J1c3RuZXNzIG9mIHRoZSBtb2RlbC4KCmBgYHtyfQpzZXQuc2VlZCgyNjcpIAoKZGYkY2FyZF90eXBlIDwtIGZhY3RvcihkZiRjYXJkX3R5cGUsIGxldmVscyA9IGMoIm5vbmJ1eWVycyIsICJidXllcnMiKSkKIyBjcmVhdGUgc3RyYXRpZmllZCB0cmFpbmluZyBhbmQgdGVtcCBzZXRzCmluZGV4X3RyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGYkY2FyZF90eXBlLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCgojIGtlZXAgYSBjb3B5IG9mIHRlc3Qgc2V0IHdpdGggY2xpZW50X2lkIGZvciBsYXRlciBUb3AtTiBjbGllbnRzLgojIGRyb3AgY2xpZW50X2lkIGZvciBib3RoIHRyYWluIGFuZCB0ZXN0c2V0LCBhcyBpdCBpcyBub3QgcmVsZXZhbnQgdG8gbW9kZWxpbmcuCmRmX3RyYWluIDwtIGRmW2luZGV4X3RyYWluLCBdICU+JSBzZWxlY3QoLWNsaWVudF9pZCkKZGZfdGVzdF9jbGllbnRpZCA8LSBkZlstaW5kZXhfdHJhaW4sIF0gCmRmX3Rlc3QgPC0gZGZfdGVzdF9jbGllbnRpZCAlPiUgc2VsZWN0KC1jbGllbnRfaWQpCmBgYAoKCiMjIDIgQ2xhc3NpZnkgdGhlIGRhdGEgYnkgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zCiMjIyAyLjEgQmFzZWxpbmUgbW9kZWw6IGxvZ2lzdGljIHJlZ3Jlc3Npb24KCkkgd2lsbCB1c2UgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBhcyBhIGJhc2VsaW5lLiBMb2dpc3RpYyByZWdyZXNzaW9uIHJlcXVpcmVzIG51bWVyaWMgaW5wdXQgdmFyaWFibGVzLiBUaGVyZWZvcmUsIEkgd2lsbCBmaXJzdCB0cmFuc2Zvcm0gdGhlIG5vbi1udW1lcmljIHZhcmlhYmxlcyBpbnRvIG51bWVyaWMgZm9ybWF0LiAKCmBgYHtyfQp1bmlxdWVfZGF0YV90eXBlcyA8LSB1bmlxdWUoc2FwcGx5KGRmLCBjbGFzcykpCmNhdCgiRGF0YSB0eXBlcyBvZiBkZjoiLCBwYXN0ZSh1bmlxdWVfZGF0YV90eXBlcywgY29sbGFwc2UgPSAiLCAiKSkKYGBgCgpgYGB7cn0KY2F0X2NvbCA8LSBjb2xuYW1lcyhkZiAlPiUgc2VsZWN0X2lmKGlzLmNoYXJhY3RlcikpCmNhdCgiQ2F0ZWdvcmljYWwgdmFyaWFibGVzIGFuZCBudW1iZXIgb2YgdW5pcXVlIGNhdGVnb3JpZXM6XG4iKQpzYXBwbHkoZGZbLCBjYXRfY29sXSwgZnVuY3Rpb24oY29sKSBsZW5ndGgodW5pcXVlKGNvbCkpKQpgYGAKQ2F0ZWdvcmljYWwgdmFyaWFibGVzIG11c3QgYmUgZW5jb2RlZCBmb3IgbG9naXN0aWMgcmVncmVzc2lvbi4gCkkgd2lsbCBjb252ZXJ0IHRoZSBiaW5hcnkgZmVhdHVyZXMgKGNhcmRfdHlwZSwgZ2VuZGVyLCBsb2FuX2lzc3VhbmNlKSBkaXJlY3RseSB0byBpbnRlZ2VyIDEgYW5kIDAuIEZvciB0aGUgbXVsdGktY2xhc3MgZmVhdHVyZXMgKGRpc3RyaWN0X25hbWUsIHJlZ2lvbiwgYWNjb3VudF9mcmVxKSwgSSB3aWxsIGFwcGx5IG9uZS1ob3QgZW5jb2RpbmcgdG8gY2F0ZWdvcmljYWwgZmVhdHVyZXMgdG8gYXZvaWQgYXNzaWduaW5nIHRoZSBjb252ZXJ0ZWQgbnVtYmVyIGFueSBvcmRpbmFsIG1lYW5pbmdzLgoKVGhlcmUgYXJlIDc3IHVuaXF1ZSBjYXRlZ29yaWVzIG9mIGRpc3RyaWN0X25hbWUsIHRoaXMgbWVhbnMsIDc3IGNvbHVtbnMgd2lsbCBiZSBnZW5lcmF0ZWQgYnkgb25lLWhvdC1lbmNvZGluZyB0aGUgZmVhdHVyZSBkaXN0cmljdF9uYW1lLiBJbiB0b3RhbCwgd2Ugd2lsbCBnZXQgbW9yZSB0aGFuIDE0MCBmZWF0dWVycy4gRGVwZW5kaW5nIG9uIHRoZSBtYWNoaW5lIGxlYXJuaW5nIHNlbGVjdGlvbiwgdGhpcyBtaWdodCBiZSBub3QgcHJvYmxlbWF0aWMuIFRyZWUtdHlwZSBtb2RlbHMgbGlrZSBkZWNpc2lvbiB0cmVlIGFuZCByYW5kb20gZm9yZXN0IGNvdWxkIHNldCB0cmVlIGRlcHRoIHRvIGxpbWl0IHRoZSB1c2FnZSBvZiB2YXJpYWJsZXMuIExvZ2lzdGljIHJlZ3Jlc3Npb24gY291bGQgdXNlIHJlZ3VsYXJpemF0aW9uIChlLmcuIEwxKSB0byBhdXRvbWF0aWNhbGx5IHNlbGVjdCByZWxldmFudCBmZWF0dXJlcyBhbmQgc2V0IG90aGVycyBhcyAwLiAKCiMjIyMgMi4xLjEgT25lIGhvdCBlbmNvZGluZyB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIApgYGB7cn0KZGZfZW5jb2RlZCA8LSBkZiAlPiUKICBtdXRhdGUoZ2VuZGVyID0gaWZlbHNlKGdlbmRlciA9PSAibWFsZSIsIDEsIDApLAogICAgICAgICBsb2FuX2lzc3VhbmNlID0gaWZlbHNlKGxvYW5faXNzdWFuY2UgPT0gIlllcyIsIDEsIDApKQoKZGZfZW5jb2RlZCRjYXJkX3R5cGUgPC0gZmFjdG9yKGRmX2VuY29kZWQkY2FyZF90eXBlLCBsZXZlbHMgPSBjKCJub25idXllcnMiLCAiYnV5ZXJzIikpCiNlbmNvZGUgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwplbmNvZGVkX2RmIDwtIGRhdGEuZnJhbWUobW9kZWwubWF0cml4KH4gMCArIC4sIGRhdGEgPSBkZl9lbmNvZGVkWywgYygiZGlzdHJpY3RfbmFtZSIsICJyZWdpb24iLCAiYWNjb3VudF9mcmVxIildKSkKCiMgcmVtb3ZlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwpkZl8gPC0gZGZfZW5jb2RlZFssIC13aGljaChuYW1lcyhkZl9lbmNvZGVkKSAlaW4lIGMoImRpc3RyaWN0X25hbWUiLCAicmVnaW9uIiwgImFjY291bnRfZnJlcSIpKV0KIyBmdWxsIGRhdGEgd2l0aCBlbmNvZGVkIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwpkZl9lbmNvZGVkIDwtIGNiaW5kKGRmXywgZW5jb2RlZF9kZikKZGZfZW5jb2RlZApgYGAKCkFmdGVyIE9uZS1ob3QtZW5jb2RpbmcsIHRoZSBkYXRhIGluY2x1ZGVzIDE0MyBpbmRlcGVuZGVudCBmZWF0dXJlcy4KCiMjIyMgMi4xLjIgc3BsaXQgZW5jb2RlZCBkYXRhZnJhbWUgdG8gdHJhaW4gYW5kIHRlc3RzZXQKYGBge3J9CiMgZHJvcCBjbGllbnRfaWQgaW4gYm90aCB0cmFpbiBhbmQgdGVzdCBzZXQuCmRmX3RyYWluX2VuY29kZWQgPC0gZGZfZW5jb2RlZFtpbmRleF90cmFpbixdICU+JSBzZWxlY3QoLWNsaWVudF9pZCkKZGZfdGVzdF9lbmNvZGVkIDwtIGRmX2VuY29kZWRbLWluZGV4X3RyYWluLF0gJT4lIHNlbGVjdCgtY2xpZW50X2lkKQpkZl90ZXN0X2VuY29kZWQkY2FyZF90eXBlIDwtIGZhY3RvcihkZl90ZXN0X2VuY29kZWQkY2FyZF90eXBlLCBsZXZlbHMgPSBsZXZlbHMoZGZfdHJhaW5fZW5jb2RlZCRjYXJkX3R5cGUpKQp4X3RyYWluX2VuY29kZWQgPC0gZGZfdHJhaW5fZW5jb2RlZCAlPiUgc2VsZWN0KC0oY2FyZF90eXBlKSkKeF90ZXN0X2VuY29kZWQgPC0gZGZfdGVzdF9lbmNvZGVkICU+JSBzZWxlY3QoLShjYXJkX3R5cGUpKQpgYGAKClNwbGl0IHRyYWluIGFuZCB0ZXN0IGRhdGFzZXQgdXNpbmcgc2FtZSBkYXRhIGluZGljZXMgYXMgZm9yIHRoZSBvcmlnaW5hbCBkYXRhIHBhcnRpdGlvbmluZy4gU28gdGhhdCBpdCBpcyBjb21wYXJhYmxlIHdpdGggbGF0ZXIgbW9kZWxzLgoKIyMjIyAyLjEuMyBhZGQgY2xhc3Mgd2VpZ2h0cyB0byB0aGUgbW9kZWwKQXMgcHJldmlvdXNseSBhbHJlYWR5IGFuYWx5c2VkLCB0aGUgZGF0YXNldCBpcyBpbWJhbGFuY2VkLCBpZiBkaXJlY3RseSB1c2luZyBpbWJhbGFuY2VkIGRhdGFzZXQgZm9yIGNsYXNzaWZpY2F0aW9uIG1heSByZXN1bHQgYmlhcyB0b3dhcmQgdGhlIG1ham9yaXR5IGNsYXNzLCBjYXVzaW5nIHBvb3IgcHJlZGljdGlvbnMgZm9yIHRoZSBtaW5vcml0eSBjbGFzcy4KYGBge3J9CnRvdGFsX3NhbXBsZXMgPC0gbGVuZ3RoKGRmX3RyYWluJGNhcmRfdHlwZSkKbl9idXllcnMgPSBsZW5ndGgoZGZfdHJhaW4kY2FyZF90eXBlW2RmX3RyYWluJGNhcmRfdHlwZSA9PSAnYnV5ZXJzJ10pCm5fbm9uYnV5ZXJzID0gdG90YWxfc2FtcGxlcyAtIG5fYnV5ZXJzCndlaWdodF9idXllcnMgPC0gdG90YWxfc2FtcGxlcyAvICgyICogbl9idXllcnMpCndlaWdodF9ub25idXllcnMgPC0gdG90YWxfc2FtcGxlcy8gKDIgKiBuX25vbmJ1eWVycykKY2F0KCJUaGVyZSBhcmUgIixuX2J1eWVycywiYnV5ZXJzLCIsIG5fbm9uYnV5ZXJzLCAibm9uYnV5ZXJzXG4iKQpjYXQoIldlaWdodHMgb2YgYnV5ZXJzOiIsIHdlaWdodF9idXllcnMsICJcbiIpCmNhdCgiV2VpZ2h0cyBvZiBub24tYnV5ZXJzOiIsIHdlaWdodF9ub25idXllcnMsICJcbiIpCmNsYXNzX3dlaWdodHMgPC0gaWZlbHNlKGRmX3RyYWluJGNhcmRfdHlwZSA9PSAnYnV5ZXJzJywgd2VpZ2h0X2J1eWVycywgd2VpZ2h0X25vbmJ1eWVycykKYGBgCgojIyMjIDIuMS40IHRyYWluIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCAxMC1mb2xkIGNyb3NzIHZhbGlkYXRpb24KYGBge3J9CmN0cmwgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJjdiIsIAogIG51bWJlciA9IDEwLCAKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnksIAogIGNsYXNzUHJvYnMgPSBUUlVFKQpscl9tb2RlbCA8LSB0cmFpbih4PSB4X3RyYWluX2VuY29kZWQsIAogICAgICAgICAgICAgICAgICAgeSA9IGRmX3RyYWluX2VuY29kZWQkY2FyZF90eXBlLCAKICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLCB0ckNvbnRyb2wgPSBjdHJsLCBtZXRyaWMgPSAiUk9DIiwgCiAgICAgICAgICAgICAgICAgICBtYXhpdCA9IDEwMDAwLHdlaWdodHMgPSBjbGFzc193ZWlnaHRzKQpgYGAKdGhlIHdhcm5pbmcgc3VnZ2VzdHMgc29tZSBvZiB0aGUgdmFyaWFibGVzIGFyZSBoaWdobHkgY29ycmVsYXRlZCB3aXRoIG90aGVycyBhbmQgbWF5IGNvbnRyaWJ1dGUgdG8gbXVsdGljb2xsaW5lYXJpdHkuIFNvIEkgd2lsbCBpZGVudGlmeSB0aGVzZSB2YXJpYWJsZXMgYW5kIGRyb3AgdGhlbS4KCiMjIyMgMi4xLjUgZHJvcCB0aGUgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzIHRvIHNvbHZlIG11bHRpY29sbGluZWFyaXR5CgpNeSBtZXRob2QgaXMgdG8gZmluZCBvdXQgdGhlIGVpZ2VudmFsdWVzIHRoYXQgYXJlIGNsb3NlIHRvIHplcm8gb3Igc2lnbmlmaWNhbnRseSBzbWFsbGVyIHRoYW4gdGhlIG90aGVycy4gVGhlc2UgbG93IGVpZ2VudmFsdWVzIGluZGljYXRlIHRoYXQgdGhlIGNvcnJlc3BvbmRpbmcgUENzICh2YXJpYWJsZXMpIGFyZSBoaWdobHkgY29ycmVsYXRlZCB3aXRoIG90aGVycyBhbmQgbWF5IGJlIGNvbnRyaWJ1dGluZyB0byBtdWx0aWNvbGxpbmVhcml0eS4gVmFyaWFibGVzIHdpdGggbG93IGVpZ2VudmFsdWVzIGNvbnRyaWJ1dGUgbGVzcyB0byBleHBsYWluaW5nIHRoZSB2YXJpYW5jZSBpbiB0aGUgZGF0YSBhbmQgYXJlIGxlc3MgaW5mb3JtYXRpdmUuIEFzIGEgcmVzdWx0LCB0aGV5IG1heSBiZSBtb3JlIGxpa2VseSB0byBvdmVybGFwIGluIHRlcm1zIG9mIHRoZSBpbmZvcm1hdGlvbiB0aGV5IHByb3ZpZGUsIGxlYWRpbmcgdG8gbXVsdGljb2xsaW5lYXJpdHkuCgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyBDYWxjdWxhdGUgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeApjb3JyZWxhdGlvbl9tYXRyaXggPC0gY29yKGRmX3RyYWluX2VuY29kZWRbLDI6MTQzXSkKIyBQZXJmb3JtIFBDQQpwY2FfcmVzdWx0IDwtIHByaW5jb21wKGNvcnJlbGF0aW9uX21hdHJpeCkKZWlnZW52YWx1ZXMgPC0gcGNhX3Jlc3VsdCRzZGV2XjIKdmFyaWFibGVfbmFtZXMgPC0gcm93bmFtZXMocGNhX3Jlc3VsdCRsb2FkaW5ncykKdGhyZXNob2xkIDwtIDAuMDEyICAjIEFkanVzdCB0aGlzIHRocmVzaG9sZCBhcyBuZWVkZWQKaW5kaWNlc19jbG9zZV90b18wIDwtIHdoaWNoKGFicyhlaWdlbnZhbHVlcykgPCB0aHJlc2hvbGQpCmRyb3BfdmFyaWFibGVfbmFtZXM8LSB2YXJpYWJsZV9uYW1lc1tpbmRpY2VzX2Nsb3NlX3RvXzBdCmRmX3RyYWluX2VuY29kZWRfcmVkdWNlZCA8LSBkZl90cmFpbl9lbmNvZGVkICU+JSBzZWxlY3QoLWRyb3BfdmFyaWFibGVfbmFtZXMpCmRmX3RyYWluX2VuY29kZWRfcmVkdWNlZCRjYXJkX3R5cGUgPC0gZmFjdG9yKGRmX3RyYWluX2VuY29kZWRfcmVkdWNlZCRjYXJkX3R5cGUsIGxldmVscyA9IGMoIm5vbmJ1eWVycyIsICJidXllcnMiKSwgb3JkZXJlZCA9IFRSVUUpCnhfdHJhaW5fZW5jb2RlZF9yZWR1Y2VkIDwtIHhfdHJhaW5fZW5jb2RlZCAlPiUgc2VsZWN0KC1kcm9wX3ZhcmlhYmxlX25hbWVzKQoKZGZfdGVzdF9lbmNvZGVkX3JlZHVjZWQgPC0gZGZfdGVzdF9lbmNvZGVkICU+JSBzZWxlY3QoLWRyb3BfdmFyaWFibGVfbmFtZXMpCmRmX3Rlc3RfZW5jb2RlZF9yZWR1Y2VkJGNhcmRfdHlwZSA8LSBmYWN0b3IoZGZfdGVzdF9lbmNvZGVkX3JlZHVjZWQkY2FyZF90eXBlLCBsZXZlbHMgPSBjKCJub25idXllcnMiLCAiYnV5ZXJzIiksIG9yZGVyZWQgPSBUUlVFKQp4X3Rlc3RfZW5jb2RlZF9yZWR1Y2VkIDwtIHhfdGVzdF9lbmNvZGVkICU+JSBzZWxlY3QoLWRyb3BfdmFyaWFibGVfbmFtZXMpCmBgYAoKCiMjIyMgMi4xLjYgU2NhbGUgdGhlIGRhdGEgCgpJdCBpcyBhIGNvbW1vbiBwcmVwcm9jZXNzaW5nIHN0ZXAgaW4gbWFjaGluZSBsZWFybmluZywgaW5jbHVkaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIHRvIGVuc3VyZSB0aGF0IGFsbCBmZWF0dXJlcyBoYXZlIHRoZSBzYW1lIHNjYWxlIGFuZCBjb250cmlidXRlIGVxdWFsbHkgdG8gdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UuIAoKYGBge3J9CiMgRGVmaW5lIHRoZSBwcmVwcm9jZXNzaW5nIG1ldGhvZCAoc2NhbGluZyB0byBbMCwgMV0pCnByZXByb2Nlc3NfbWV0aG9kIDwtIHByZVByb2Nlc3MoZGZfdHJhaW5fZW5jb2RlZF9yZWR1Y2VkLCBtZXRob2QgPSBjKCJyYW5nZSIpKQojIEFwcGx5IHRoZSBwcmVwcm9jZXNzaW5nIG1ldGhvZCB0byBib3RoIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXRzCmRmX3RyYWluX2VuY29kZWRfcmVkdWNlZF9zY2FsZWQgPC0gcHJlZGljdChwcmVwcm9jZXNzX21ldGhvZCwgZGZfdHJhaW5fZW5jb2RlZF9yZWR1Y2VkKQpkZl90ZXN0X2VuY29kZWRfcmVkdWNlZF9zY2FsZWQgPC0gcHJlZGljdChwcmVwcm9jZXNzX21ldGhvZCwgZGZfdGVzdF9lbmNvZGVkX3JlZHVjZWQpCnhfdHJhaW5fZW5jb2RlZF9yZWR1Y2VkX3NjYWxlZCA8LSBkZl90cmFpbl9lbmNvZGVkX3JlZHVjZWRfc2NhbGVkWywtd2hpY2gobmFtZXMoZGZfdHJhaW5fZW5jb2RlZF9yZWR1Y2VkX3NjYWxlZCkgPT0gImNhcmRfdHlwZSIpXQp4X3Rlc3RfZW5jb2RlZF9yZWR1Y2VkX3NjYWxlZCA8LSBkZl90ZXN0X2VuY29kZWRfcmVkdWNlZF9zY2FsZWRbLC13aGljaChuYW1lcyhkZl90ZXN0X2VuY29kZWRfcmVkdWNlZF9zY2FsZWQpID09ICJjYXJkX3R5cGUiKV0KYGBgCgoKIyMjIyAyLjEuNyB0cmFpbiBsb2dpc3RpYyByZWdyZXNzaW9uIG9uIHRoZSBzY2FsZWQgZGF0YSAKCiMjIyMjIDIuMS43LjEgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIG1ldHJpY3MgZm9yIHRoZSBwcmVkaWN0aW9uIHJlc3VsdHMgb2YgZWFjaCBtb2RlbAoKYGBge3J9Cm1ldHJpY3NfdGFibGUgPC0gZnVuY3Rpb24ocHJlZF9sYWJlbHMsIHRydWVfbGFiZWxzLCBpZHgsIHJldHVybl9jb25mX21hdHJpeCA9IEZBTFNFKXsKICBleHBlY3RlZF9sZXZlbHMgPC0gYygibm9uYnV5ZXJzIiwgImJ1eWVycyIpCiAgcHJlZF9sYWJlbHMgPC0gZmFjdG9yKHByZWRfbGFiZWxzLCBsZXZlbHMgPSBleHBlY3RlZF9sZXZlbHMpCiAgdHJ1ZV9sYWJlbHMgPC0gZmFjdG9yKHRydWVfbGFiZWxzLCBsZXZlbHMgPSBleHBlY3RlZF9sZXZlbHMpCiAgY29uZl9tYXRyaXggPC0gY29uZnVzaW9uTWF0cml4KHByZWRfbGFiZWxzLCB0cnVlX2xhYmVscywgcG9zaXRpdmUgPSAnYnV5ZXJzJykKICB0cnVlX2ludCA8LSBpZmVsc2UodHJ1ZV9sYWJlbHMgPT0gJ25vbmJ1eWVycycsIDAsIDEpCiAgcHJlZF9pbnQgPC0gaWZlbHNlKHByZWRfbGFiZWxzID09ICdub25idXllcnMnLCAwLCAxKQogIAogIFRQIDwtIGNvbmZfbWF0cml4JHRhYmxlWydidXllcnMnLCdidXllcnMnXSAgIyBUcnVlIFBvc2l0aXZlcwogIFROIDwtIGNvbmZfbWF0cml4JHRhYmxlWydub25idXllcnMnLCdub25idXllcnMnXSAjIFRydWUgTmVnYXRpdmVzCiAgRlAgPC0gY29uZl9tYXRyaXgkdGFibGVbJ2J1eWVycycsJ25vbmJ1eWVycyddICAjIEZhbHNlIFBvc2l0aXZlcwogIEZOIDwtIGNvbmZfbWF0cml4JHRhYmxlWydub25idXllcnMnLCdidXllcnMnXSAjIEZhbHNlIE5lZ2F0aXZlcwogIHJvY19vYmogPC0gcm9jKHRydWVfaW50LCBwcmVkX2ludCkKICBBdUMgPC0gYXVjKHJvY19vYmopCgogIHJlc3VsdCA8LSBkYXRhLmZyYW1lKAogICAgbW9kZWxfaW5kZXggPSBpZHgsCiAgICBmMV9zY29yZSA9IGNvbmZfbWF0cml4JGJ5Q2xhc3NbIkYxIl0sCiAgICBhY2N1cmFjeSA9IGNvbmZfbWF0cml4JG92ZXJhbGxbIkFjY3VyYWN5Il0sCiAgICBBdUMgPSBBdUMsCiAgICBrb2hlbmthcHBhID0gY29uZl9tYXRyaXgkb3ZlcmFsbFsiS2FwcGEiXSwKICAgIHByZWNpc2lvbiA9IGNvbmZfbWF0cml4JGJ5Q2xhc3NbIlBvcyBQcmVkIFZhbHVlIl0sCiAgICByZWNhbGwgPSBjb25mX21hdHJpeCRieUNsYXNzWyJTZW5zaXRpdml0eSJdKQogIGlmKHJldHVybl9jb25mX21hdHJpeCl7CiAgICByZXR1cm4oYXMuZGF0YS5mcmFtZShjb25mX21hdHJpeCR0YWJsZSkpCiAgfQogIGVsc2V7CiAgICByZXR1cm4ocmVzdWx0KX19CmBgYAoKCiMjIyMjIDIuMS43LjIgaHlwZXJwYXJhbWV0ZXIgdHVuaW5nCgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyBEZWZpbmUgdGhlIGh5cGVycGFyYW1ldGVyIGdyaWQKaHlwZXJfZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBhbHBoYSA9IGMoMCwgMSksCiAgbGFtYmRhID0gYygwLCAwLjAwMDEsIDAuMDAxLCAwLjAxLCAwLjEpLAogIGl0ZXJzID0gYygxMDAwLCA1MDAwLCAyMDAwMCkpCgpscl9yZXN1bHRzX3RyYWluIDwtIGRhdGEuZnJhbWUoKQpscl9yZXN1bHRzX3Rlc3QgPC0gZGF0YS5mcmFtZSgpCmxyX21vZGVsc19saXN0IDwtIGxpc3QoKQpscl9wcmVkaWN0aW9uc190ZXN0X2xpc3QgPC0gbGlzdCgpCgojIFBlcmZvcm0gZ3JpZCBzZWFyY2gKZm9yIChpIGluIDE6bnJvdyhoeXBlcl9ncmlkKSkgewogIGFscGhhIDwtIGh5cGVyX2dyaWQkYWxwaGFbaV0KICBsYW1iZGEgPC0gaHlwZXJfZ3JpZCRsYW1iZGFbaV0KICBpdGVycyA8LSBoeXBlcl9ncmlkJGl0ZXJzW2ldCiAgCiAgY29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gImN2IiwKICBudW1iZXIgPSAxMCwKICBjbGFzc1Byb2JzID0gVFJVRSwKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnksCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUKKQoKIyB0cmFpbiBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsCiAgc2V0LnNlZWQoMTIzKSAKICBscl9tb2RlbCA8LSB0cmFpbigKICAgIHggPSB4X3RyYWluX2VuY29kZWRfcmVkdWNlZF9zY2FsZWQsCiAgICB5ID0gZGZfdHJhaW5fZW5jb2RlZF9yZWR1Y2VkX3NjYWxlZCRjYXJkX3R5cGUsCiAgICB3ZWlnaHRzID0gY2xhc3Nfd2VpZ2h0cywKICAgIG1ldGhvZCA9ICJnbG1uZXQiLAogICAgdHJDb250cm9sID0gY29udHJvbCwKICAgIG1ldHJpYyA9ICJST0MiLAogICAgbWF4aXQgPSBpdGVycywKICAgIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQoCiAgICAgIGFscGhhID0gYWxwaGEsCiAgICAgIGxhbWJkYSA9IGxhbWJkYSkpCiAgbHJfbW9kZWxzX2xpc3RbW2ldXSA8LSBscl9tb2RlbAogIAogICMgRXZhbHVhdGUgbW9kZWwgb3B0aW1pemF0aW9uICAKICB0cmFpbl9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGxyX21vZGVsLCBkZl90cmFpbl9lbmNvZGVkX3JlZHVjZWRfc2NhbGVkLCB0eXBlID0gInByb2IiKQogICN0cmFpbl9wcmVkaWN0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKHRyYWluX3ByZWRpY3Rpb25zKQogIHRyYWluX3ByZWRpY3Rpb25zJGRlY2lzaW9uIDwtIGFzLmZhY3RvcihpZmVsc2UodHJhaW5fcHJlZGljdGlvbnMkbm9uYnV5ZXJzID4gdHJhaW5fcHJlZGljdGlvbnMkYnV5ZXJzLCAnbm9uYnV5ZXJzJywgJ2J1eWVycycpKQogIHJlc3VsdCA9IG1ldHJpY3NfdGFibGUodHJhaW5fcHJlZGljdGlvbnMkZGVjaXNpb24sIGRmX3RyYWluX2VuY29kZWRfcmVkdWNlZF9zY2FsZWQkY2FyZF90eXBlLCBpKQogIGxyX3Jlc3VsdHNfdHJhaW4gPC0gcmJpbmQobHJfcmVzdWx0c190cmFpbiwgcmVzdWx0KQogIAogICMgRXZhbHVhdGUgbW9kZWwgb24gdGVzdHNldAogIHRlc3RfcHJlZGljdGlvbnMgPC0gcHJlZGljdChscl9tb2RlbCwgZGZfdGVzdF9lbmNvZGVkX3JlZHVjZWRfc2NhbGVkLCB0eXBlID0gInByb2IiKQogIHRlc3RfcHJlZGljdGlvbnMgPC0gY2JpbmQodGVzdF9wcmVkaWN0aW9ucywgZGZfdGVzdF9jbGllbnRpZCkgJT4lIHNlbGVjdChjKGJ1eWVycywgY2xpZW50X2lkKSklPiUgbXV0YXRlKGlkeCA9IDE6bnJvdyh0ZXN0X3ByZWRpY3Rpb25zKSwgZGVjaXNpb24gPSBhcy5mYWN0b3IoaWZlbHNlKGJ1eWVycyA+PSAwLjUsICdidXllcnMnLCAnbm9uYnV5ZXJzJykpKSAKCiAgbHJfcHJlZGljdGlvbnNfdGVzdF9saXN0W1tpXV0gPC0gdGVzdF9wcmVkaWN0aW9ucwogIHJlc3VsdCA9IG1ldHJpY3NfdGFibGUodGVzdF9wcmVkaWN0aW9ucyRkZWNpc2lvbiwgZGZfdGVzdF9lbmNvZGVkX3JlZHVjZWRfc2NhbGVkJGNhcmRfdHlwZSwgaSkKICBscl9yZXN1bHRzX3Rlc3QgPC0gcmJpbmQobHJfcmVzdWx0c190ZXN0LCByZXN1bHQpfQoKIyBiaW5kIHRyYWluIGFuZCB0ZXN0IG1ldHJpY3MKY29sbmFtZXMobHJfcmVzdWx0c190cmFpbikgPC0gcGFzdGUwKGNvbG5hbWVzKGxyX3Jlc3VsdHNfdHJhaW4pLCAiX3RyYWluIikKY29sbmFtZXMobHJfcmVzdWx0c190ZXN0KSA8LSBwYXN0ZTAoY29sbmFtZXMobHJfcmVzdWx0c190ZXN0KSwgIl90ZXN0IikKbHJfcmVzdWx0cyA8LSBjYmluZChscl9yZXN1bHRzX3RyYWluLCBscl9yZXN1bHRzX3Rlc3QpCmxyX3Jlc3VsdHMgPC0gbmEub21pdChscl9yZXN1bHRzKSAlPiUgc2VsZWN0KC1tb2RlbF9pbmRleF90ZXN0KSAlPiUKICBhcnJhbmdlKGRlc2MoZjFfc2NvcmVfdGVzdCksIGRlc2MoQXVDX3Rlc3QpLCBkZXNjKGFjY3VyYWN5X3Rlc3QpLCBkZXNjKGtvaGVua2FwcGFfdGVzdCkpCmxyX3Jlc3VsdHMKYGBgCldpdGggZ3JpZCBzZWFyY2hpbmcsIEkgZ2V0IGV2YWx1YXRpb24gcmVzdWx0cyBvZiAzMCBtb2RlbHMuIFRoZSBkYXRhIGlzIHNvcnRlZCBieSB0ZXN0c2V0IGYxLXNjb3JlLCBBdUMsIGFjY3VyYWN5LCBhbmQga29oZW5rYXBwYS4gVGhlIGVpZ2h0aCBtb2RlbCBoYXMgdGhlIGJlc3QgcGVyZm9ybWFuY2UuCgpUbyBiZXR0ZXIgdmlzdWFsaXplIHRoZSByZXN1bHQsIEkgd2lsbCB1c2UgbGluZSBwbG90cyB0byB2aXN1YWxpemUgdGhlIHVwcGVyIHRhYmxlLiBMaW5lIHBsb3RzIGFyZSB1c3VhbGx5IHVzZWQgdG8gcHJlc2VudCB0cmVuZHMuIEhvd2V2ZXIsIGl0J3MgcGVyZmVjdGx5IGZpbmUgdG8gdXNlIGxpbmVzIHRvIGNvbm5lY3QgcG9pbnRzIHdpdGhpbiByb3dzIHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gZ3JvdXBzIGFuZCBoaWdobGlnaHQgcmVsYXRpb25zaGlwcyBpbiB0aGUgZGF0YSwgYmVjYXVzZSBpdCBtYWtlcyB0aGUgdmlzdWFsaXphdGlvbiBjbGVhcmVyLiAKCmBgYHtyfQptZXRyaWNfb3JkZXIgPC0gYygiZjFfc2NvcmVfdHJhaW4iLCJmMV9zY29yZV90ZXN0IiwiQXVDX3RyYWluIiwiQXVDX3Rlc3QiLCJhY2N1cmFjeV90cmFpbiIsImFjY3VyYWN5X3Rlc3QiLCAia29oZW5rYXBwYV90cmFpbiIsICJrb2hlbmthcHBhX3Rlc3QiLCAicHJlY2lzaW9uX3RyYWluIiwgInByZWNpc2lvbl90ZXN0IiwicmVjYWxsX3RyYWluIiwicmVjYWxsX3Rlc3QiKQpldmFsX2xvbmcgPC0gbHJfcmVzdWx0cyAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGFsbF9vZihtZXRyaWNfb3JkZXIpLCBuYW1lc190byA9ICdtZXRyaWNzJywgdmFsdWVzX3RvID0gJ3ZhbHVlcycpCgpnZ3Bsb3QoZXZhbF9sb25nLCBhZXMoeCA9IGZhY3RvcihtZXRyaWNzLCBsZXZlbHMgPSBtZXRyaWNfb3JkZXIpLCB5ID0gdmFsdWVzLCBncm91cCA9IG1vZGVsX2luZGV4X3RyYWluKSkgKwpnZW9tX2xpbmUoYWVzKGNvbG9yID0gbW9kZWxfaW5kZXhfdHJhaW4pKSArCmdlb21fcG9pbnQoYWVzKGNvbG9yID0gbW9kZWxfaW5kZXhfdHJhaW4pKSArCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKeGxhYigiIikgKwpnZ3RpdGxlKCJMb2dpc3RpYyByZWdyZXNzaW9uOiBFdmFsdWF0aW9uIHdpdGggMTAtZm9sZCBDcm9zcyBWYWxpZGF0aW9uIikKYGBgCgpNb3N0IG9mIHRoZSBsaW5lcyBpbiB0aGUgcGxvdCBoYXZlIHZlcnkgc2ltaWxhciB2YWx1ZXMgZXNwZWNpYWxseSBpbiB0aGUgdHJhaW5zZXQsIGV4Y2VwdCBvbmUgbW9kZWwgd2l0aCBzaWduaWZpY2FudCBoaWdoZXIgcmVjYWxsIGJ1dCBsb3dlciBhY2N1cmFjeSBBdUMga29oZW5rYXBwYS4gCgojIyMjIDIuMS44IGJlc3QgbW9kZWwgYW5kIGV4cGxhaW4gaXQKCkJhc2VkIG9uIHRoZSB0YWJsZSBhbmQgbGluZSBwbG90LCBJIHdpbGwgZm9jdXMgb24gdGhlIHRvcCA2IG1vZGVscyB3aXRoIGxhcmdlc3QgbWV0cmljIHZhbHVlcy4KCkZpbmQgdGhlIGNvcnJlc3BvbmRpbmcgaHlwZXJwYXJhbWV0ZXJzIG9mIHRoZSBUT1AgNiBiZXN0IG1vZGVscy4KYGBge3J9CmlkeCA8LSBscl9yZXN1bHRzWzE6NiwnbW9kZWxfaW5kZXhfdHJhaW4nXQp0b3BfNl9wYXJhbWV0ZXJzIDwtIGh5cGVyX2dyaWRbaWR4LF0KdG9wXzZfcGFyYW1ldGVycwpgYGAKClNlbGVjdCB0aGUgZmlyc3Qgb25lIGFzIHRoZSBiZXN0IGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgYW5kIGV4cGxhaW4gaXQuCgpgYGB7cn0KZmluYWxfbHJfbW9kZWwgPC0gbHJfbW9kZWxzX2xpc3RbWzFdXQojIGV4cGxhaW4gdGhlIG1vZGVsCmV4cGxhaW5lcl9sciA8LSBleHBsYWluKGZpbmFsX2xyX21vZGVsLCBkYXRhID0gZGZfdHJhaW5fZW5jb2RlZF9yZWR1Y2VkX3NjYWxlZCwgeT1hcy5udW1lcmljKGRmX3RyYWluX2VuY29kZWRfcmVkdWNlZF9zY2FsZWQkY2FyZF90eXBlKSwgbGFiZWw9J2xvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwnKQpgYGAKIyMjIyAyLjEuOSBUb3AtMTAgZmVhdHVyZXMKClRvcCAxMCBmZWF0dXJlcyBieSBwZXJtdXRhdGlvbiBpbXBvcnRhbmNlIHJhbmtpbmc6IApgYGB7cn0Kc2V0LnNlZWQoMTIzKQpwZmlfbHIgPC0gbW9kZWxfcGFydHMoZXhwbGFpbmVyX2xyLCB0eXBlID0gInZhcmlhYmxlX2ltcG9ydGFuY2UiLCBOID0gMTAwMCkKcGxvdChwZmlfbHJbMToxMSxdLCBzaG93X2JveHBsb3RzID0gVFJVRSkgKyBnZ3RpdGxlKCJMb2dpc3RpYyByZWdyZXNzaW9uOiBUb3AgMTAgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMiKQpgYGAKVGhlIHBsb3QgdGVsbHMsIHdoZW4gbWl4aW5nIHVwIHRoZSB2YWx1ZXMgb2Ygb25lIGZlYXR1cmUsIGhvdyBtdWNoIHdpbGwgdGhlIG1vZGVsIG5lZ2F0aXZlbHkgYWZmZWN0ZWQuIExhcmdlciBjaGFuZ2VzIGluZGljYXRpbmcgaGlnaGVyIGltcG9ydGFuY2Ugb2YgdGhpcyBmZWF0dXJlLiBGb3IgZXhhbXBsZSwgdGhlIGZlYXR1cmUgb3JkZXJfVVZFUiBoYXMgdGhlIGhpZ2hlc3QgaW1wb3J0YW5jZSBoZXJlLiBUaGUgbW9kZWwgd2l0aCBvcmlnaW5hbCBmdWxsIGZlYXR1cmVzIGhhcyB0aGUgQXVDIG9mIDAuNTY1LiBXaGVuIG1peGluZyB1cCB0aGUgdmFsdWVzIG9mIG9yZGVyX1VWRVIsIHRoZSBtb2RlbCBBdUMgaXMgcmVkdWNlZCB0byAwLjUzOSwgdGhlIGRpZmZlcmVuY2UgaXMgMC4wMjYuIApXZSBjb3VsZCBzZWUgdGhhdCwgb25seSB0aGUgdG9wIDUgZmVhdHVyZXMgKG9yZGVyX1VWRVIsIG9yZGVyX1NJUE8sIG9yZGVyX09USEVSLCBucl9tdW5pY2lwYWxpdGllc180LCBucl9lbnRlcnByZW5ldXJzX3Blcl8xMDAwX2luaGFiaXRhbnRzKSBoYXZlIHZhbGlkIGltcG9ydGFuY2UvaW5mbHVlbmNlL2NvbnRyaWJ1dGlvbiB0byB0aGUgbW9kZWwgcGVyZm9ybWFuY2UuIFRoZSB0b3AgNiB0byAxMCBmZWF0dXJlcyBoYXZlIGFsbW9zdCBudWxsIGluZmx1ZW5jZS4KCgojIyMjIDIuMS4xMCBUb3AtMTAgY2xpZW50cwoKTGV0J3Mgb2JzZXJ2ZSB0aGUgdG9wIDEwIGNsaWVudHMgd2l0aCB0aGUgaGlnaGVzdCBwcmVkaWN0aW9uIHByb2JhYmlsaXRpZXMuCgpgYGB7cn0KZmluYWxfbHJfcHJlZGljdGlvbnNfdGVzdCA8LSBscl9wcmVkaWN0aW9uc190ZXN0X2xpc3RbW2lkeFsxXV1dIApUb3Bfbl9jbGllbnRzX2xyIDwtIGZpbmFsX2xyX3ByZWRpY3Rpb25zX3Rlc3QgJT4lIGFycmFuZ2UoZGVzYyhidXllcnMpKQpUb3Bfbl9jbGllbnRzX2xyCmBgYApPdmVyYWxsLCB0aGUgcHJlZGljdGlvbiBwcm9iYWJpbGl0aWVzIGFyZSBub3QgdGhhdCBoaWdoLCB3aXRoIGEgbWF4aW1hbCBvZiAwLjcuCgpDaGVjayBleGFjdGx5IHdoaWNoIGZlYXR1cmVzIGNvbnRyaWJ1dGVkIHRvIHRoZSBwcmVkaWN0aW9ucy4KYGBge3J9Cm4gPSAxMApwbG90X2xpc3QgPC0gbGlzdCgpCmZvciAoaSBpbiBzZXEoMSwgbikpIHsKICBpZHggPSBUb3Bfbl9jbGllbnRzX2xyW2ksM10KICBjbGllbnRfaWQgPSBUb3Bfbl9jbGllbnRzX2xyW2ksMl0KICBiZHBfbHIgPC0gcHJlZGljdF9wYXJ0cyhleHBsYWluZXJfbHIsIG5ld19vYnNlcnZhdGlvbiA9IGRmX3Rlc3RfZW5jb2RlZF9yZWR1Y2VkX3NjYWxlZFtpZHgsIF0pCiAgcCA8LSBwbG90KGJkcF9scikgKyBnZ3RpdGxlKHBhc3RlKCJMb2dpc3RpYyByZWdyZXNzaW9uIC0gQ2xpZW50X2lkICIsY2xpZW50X2lkKSkKICBwbG90X2xpc3RbW2ldXSA8LSBwfQoKZm9yIChpIGluIDE6bikgewogIHByaW50KHBsb3RfbGlzdFtbaV1dKQp9CmBgYAoKSW4gYWxsIHRvcCAxMCBjbGllbnRzLCB0aGUgZmVhdHVyZXMgb3JkZXJfU0lQTywgb3JkZXJfVVZFUiwgb3JkZXJfT1RIRVIgYXJlIHRoZSBtYWluIGNvbnRyaWJ1dG9ycy4KCiMjIyMgMi4xLjExIEV2YWx1YXRpb24KCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQptZXRyaWNfcmVzdWx0cyA8LSBkYXRhLmZyYW1lKCkKbHJfcmVzdWx0IDwtIG1ldHJpY3NfdGFibGUoZmluYWxfbHJfcHJlZGljdGlvbnNfdGVzdCRkZWNpc2lvbiwgZGZfdGVzdF9lbmNvZGVkX3JlZHVjZWRfc2NhbGVkJGNhcmRfdHlwZSwgJ0xvZ2lzdGljIHJlZ3Jlc3Npb24nKQptZXRyaWNfcmVzdWx0cyA8LSByYmluZChscl9yZXN1bHQsIG1ldHJpY19yZXN1bHRzKQpgYGAKClJlY2VpdmVyIE9wZXJhdGluZyBDaGFyYWN0ZXJpc3RpYyBjdXJ2ZSAoUk9DKSAKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnBsb3Rfcm9jX2F1YyA8LSBmdW5jdGlvbihtb2RlbF9wcmVkX3Rlc3QsIG1vZGVsX3RpdGxlKSB7CiAgY29tYmluZWRfZGYgPC0gY2JpbmQobW9kZWxfcHJlZF90ZXN0LCBkZl90ZXN0KSAlPiUgc2VsZWN0KGJ1eWVycywgY2FyZF90eXBlKQogIGNvbWJpbmVkX2RmJGNhcmRfdHlwZSA8LSBmYWN0b3IoY29tYmluZWRfZGYkY2FyZF90eXBlLCBsZXZlbHMgPSBjKCJidXllcnMiLCAibm9uYnV5ZXJzIikpCiAgcm9jX29iaiA8LSByb2MocmVzcG9uc2UgPSBjb21iaW5lZF9kZiRjYXJkX3R5cGUsIHByZWRpY3RvciA9IGNvbWJpbmVkX2RmJGJ1eWVycykKICBtb2RlbF90ZXN0X2F1YyA8LSBhdWMocm9jX29iaikKICBtb2RlbF90ZXN0X3JvYyA8LSByb2NfY3VydmUoY29tYmluZWRfZGYsIHRydXRoID0gY2FyZF90eXBlLCBidXllcnMpCiAgZ2dwbG90KGRhdGEgPSBtb2RlbF90ZXN0X3JvYywgYWVzKHggPSAxIC0gc3BlY2lmaWNpdHksIHkgPSBzZW5zaXRpdml0eSkpICsKICAgIGdlb21fcGF0aCgpICsKICAgIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIpICsKICAgIGxhYnModGl0bGUgPSBtb2RlbF90aXRsZSwKICAgICAgICAgc3VidGl0bGUgPSBwYXN0ZTAoIkFVQyA9ICIsIHJvdW5kKG1vZGVsX3Rlc3RfYXVjLCAyKSkpICsKICAgIHhsYWIoIkZhbHNlIFBvc2l0aXZlIFJhdGUgKEZQUikiKSArCiAgICB5bGFiKCJUcnVlIFBvc2l0aXZlIFJhdGUgKFRQUikiKSArCiAgICBjb29yZF9lcXVhbCgpfQoKcGxvdF9yb2NfYXVjKGZpbmFsX2xyX3ByZWRpY3Rpb25zX3Rlc3QsICJMb2dpc3RpYyByZWdyZXNzaW9uOiBST0MiKQpgYGAKClRoZSB4LWF4aXMgRlBSIG9mIFJPQyByZXByZXNlbnRzIGZhbHNlIHBvc2l0aXZlIHJhdGUsIGFuZCB0aGUgeS1heGlzIFRQUiBmb3IgdHJ1ZSBwb3NpdGl2ZSByYXRlLgoKVGhlIEFVQyBtZWFzdXJlcyB0aGUgYXJlYSB1bmRlcm5lYXRoIHRoZSBlbnRpcmUgUk9DIGN1cnZlIGZyb20gKDAsMCkgdG8gKDEsMSkuIEl0IGlzIGFuIGFnZ3JlZ2F0ZSBtZWFzdXJlIG9mIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlIGFjcm9zcyBhbGwgY2xhc3NpZmljYXRpb24gdGhyZXNob2xkcy4gQW4gQVVDIG9mIDAuNSBzdWdnZXN0cyBubyBkaXNjcmltaW5hdGl2ZSBwb3dlciBhbmQgYW4gQVVDIG9mIDEuMCByZXByZXNlbnRzIHBlcmZlY3QgcHJlZGljdGlvbi4KCkluIG15IFJPQyBncmFwaCwgdGhlIEFVQyBpcyAwLjUzLCB3aGljaCBzdWdnZXN0cyB0aGF0IHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGlzIHBlcmZvcm1pbmcgb25seSBzbGlnaHRseSBiZXR0ZXIgdGhhbiByYW5kb20gZ3Vlc3NpbmcgZm9yIHRoZSBnaXZlbiB0YXNrLgoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmNvbmZfbWF0cml4IDwtIG1ldHJpY3NfdGFibGUoZmluYWxfbHJfcHJlZGljdGlvbnNfdGVzdCRkZWNpc2lvbiwgZGZfdGVzdF9lbmNvZGVkX3JlZHVjZWRfc2NhbGVkJGNhcmRfdHlwZSwgJ2xvZ2lzdGljIHJlZ3Jlc3Npb24nLCByZXR1cm5fY29uZl9tYXRyaXg9VFJVRSkKZ2dwbG90KGNvbmZfbWF0cml4LCBhZXMoUmVmZXJlbmNlLCBQcmVkaWN0aW9uLCBmaWxsPSBGcmVxKSkgKwogICAgICAgIGdlb21fdGlsZSgpICsgZ2VvbV90ZXh0KGFlcyhsYWJlbD1GcmVxKSkgKwogICAgICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJ3aGl0ZSIsIGhpZ2g9IiMwMDkxOTQiKSArCiAgICAgICAgbGFicyh4ID0gIlJlZmVyZW5jZSIseSA9ICJQcmVkaWN0aW9uIiwgdGl0bGUgPSAiTG9naXN0aWMgcmVncmVzc2lvbjogQ29uZnVzaW9uIG1hdHJpeCIpIApgYGAKVGhlIGNvbmZ1c2lvbiBtYXRyaXggdGVsbHMsIG92ZXIgaGFsZiBvZiB0aGUgYnV5ZXJzICg4MSBvZiAxNDkpIGFyZSBmYWxzZSBwcmVkaWN0ZWQgYXMgbm9uYnV5ZXJzLiBQcmVkaWN0aW9ucyBvbiBub25idXllcnMgYXJlIHNsaWdodGx5IGJldHRlciwgMjU3IG9mIDc1MCBub25idXllcnMgYXJlIGZhbHNlIHByZWRpY3RlZCBhcyBidXllcnMuCgpgYGB7cn0KbWV0cmljX3Jlc3VsdHMKYGBgCgpUaGUgYmVzdCBsb2dpc3RpYyByZWdyZXNzaW9uIGhhcyB3ZWFrIHBlcmZvcm1hbmNlLCBpbmRpY2F0aW5nIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgbG9nIG9kZHMgb2YgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSAoYnV5ZXJzIG9yIG5vbmJ1eWVycykgYW5kIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgaXMgbm90IGxpbmVhci4KCiMjIyAyLjIgRGVjaXNpb24gdHJlZQoKIyMjIyAyLjIuMSB0cmFpbiBhbmQgdHVuZSBoeXBlcnBhcmFtZXRlcnMgCgpEZWNpc2lvbiB0cmVlIGNvdWxkIGhhbmRsZSBjYXRlZ29yaWNhbCBkYXRhIGFuZCB1bnNjYWxlZCBkYXRhLCBzbyBpIHdpbGwgdXNlIHRoZSBvcmlnaW5hbCBub3QtZW5jb2RlZCBkYXRhIHdpdGggdGhlIHNhbWUgZGF0YSBzcGxpdGluZyBpbmRpY2VzLgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShycGFydCkKCiMgRGVmaW5lIHRoZSBoeXBlcnBhcmFtZXRlciBncmlkIGZvciBncmlkIHNlYXJjaApoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIG1pbnNwbGl0ID0gYygxMCwgMjAsIDQwKSwKICBtaW5idWNrZXQgPSBjKDMsIDcsIDEyKSwKICBtYXhkZXB0aCA9IGMoMywgNSwgMTApLAogIGNwID0gYygwLjAxLCAwLjEsIDAuNSkpCgp0cmVlX3Jlc3VsdHNfdHJhaW4gPC0gZGF0YS5mcmFtZSgpCnRyZWVfcmVzdWx0c190ZXN0IDwtIGRhdGEuZnJhbWUoKQp0cmVlX21vZGVsc19saXN0IDwtIGxpc3QoKQp0cmVlX3ByZWRpY3Rpb25zX3Rlc3RfbGlzdCA8LSBsaXN0KCkKIyBQZXJmb3JtIGdyaWQgc2VhcmNoCmZvciAoaSBpbiAxOm5yb3coaHlwZXJfZ3JpZCkpIHsKICBjb250cm9sIDwtIHJwYXJ0LmNvbnRyb2woCiAgICBtaW5zcGxpdCA9IGh5cGVyX2dyaWQkbWluc3BsaXRbaV0sCiAgICBtaW5idWNrZXQgPSBoeXBlcl9ncmlkJG1pbmJ1Y2tldFtpXSwKICAgIG1heGRlcHRoID0gaHlwZXJfZ3JpZCRtYXhkZXB0aFtpXSwKICAgIGNwID0gaHlwZXJfZ3JpZCRjcFtpXSwKICAgIG1ldGhvZD0nY3YnLCAKICAgIG51bWJlcj0xMCwKICAgIHdlaWdodHMgPSBjbGFzc193ZWlnaHRzKQogIHNldC5zZWVkKDEyMykgCiAgdHJlZV9tb2RlbCA8LSBycGFydCgKICAgIGNhcmRfdHlwZSB+IC4sCiAgICBkYXRhID0gZGZfdHJhaW4sCiAgICBtZXRob2QgPSAiY2xhc3MiLAogICAgY29udHJvbCA9IGNvbnRyb2wpCiAgdHJlZV9tb2RlbHNfbGlzdFtbaV1dIDwtIHRyZWVfbW9kZWwKICAjIEV2YWx1YXRlIG1vZGVsIG9wdGltaXphdGlvbiAgCiAgdHJhaW5fcHJlZGljdGlvbnMgPC0gcHJlZGljdCh0cmVlX21vZGVsLCBkZl90cmFpbiwgdHlwZSA9ICJwcm9iIikKICB0cmFpbl9wcmVkaWN0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKHRyYWluX3ByZWRpY3Rpb25zKQogIHRyYWluX3ByZWRpY3Rpb25zJGRlY2lzaW9uIDwtIGFzLmZhY3RvcihpZmVsc2UodHJhaW5fcHJlZGljdGlvbnMkbm9uYnV5ZXJzID4gdHJhaW5fcHJlZGljdGlvbnMkYnV5ZXJzLCAnbm9uYnV5ZXJzJywgJ2J1eWVycycpKQogIAogIHJlc3VsdCA9IG1ldHJpY3NfdGFibGUodHJhaW5fcHJlZGljdGlvbnMkZGVjaXNpb24sIGRmX3RyYWluJGNhcmRfdHlwZSwgaSkKICB0cmVlX3Jlc3VsdHNfdHJhaW4gPC0gcmJpbmQodHJlZV9yZXN1bHRzX3RyYWluLCByZXN1bHQpCiAgCiAgIyBFdmFsdWF0ZSBtb2RlbCBvbiB0ZXN0c2V0CiAgdGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHRyZWVfbW9kZWwsIGRmX3Rlc3QsIHR5cGUgPSAicHJvYiIpCiAgdGVzdF9wcmVkaWN0aW9ucyA8LSBjYmluZCh0ZXN0X3ByZWRpY3Rpb25zLCBkZl90ZXN0X2NsaWVudGlkKSAlPiUgc2VsZWN0KGMoYnV5ZXJzLCBjbGllbnRfaWQpKSU+JSBtdXRhdGUoaWR4ID0gMTpucm93KHRlc3RfcHJlZGljdGlvbnMpLCBkZWNpc2lvbiA9IGFzLmZhY3RvcihpZmVsc2UoYnV5ZXJzID49IDAuNSwgJ2J1eWVycycsICdub25idXllcnMnKSkpIAoKICB0cmVlX3ByZWRpY3Rpb25zX3Rlc3RfbGlzdFtbaV1dIDwtIHRlc3RfcHJlZGljdGlvbnMKICByZXN1bHQgPSBtZXRyaWNzX3RhYmxlKHRlc3RfcHJlZGljdGlvbnMkZGVjaXNpb24sIGRmX3Rlc3QkY2FyZF90eXBlLCBpKQogIHRyZWVfcmVzdWx0c190ZXN0IDwtIHJiaW5kKHRyZWVfcmVzdWx0c190ZXN0LCByZXN1bHQpfQoKIyBiaW5kIHRyYWluIGFuZCB0ZXN0IG1ldHJpY3MKY29sbmFtZXModHJlZV9yZXN1bHRzX3RyYWluKSA8LSBwYXN0ZTAoY29sbmFtZXModHJlZV9yZXN1bHRzX3RyYWluKSwgIl90cmFpbiIpCmNvbG5hbWVzKHRyZWVfcmVzdWx0c190ZXN0KSA8LSBwYXN0ZTAoY29sbmFtZXModHJlZV9yZXN1bHRzX3Rlc3QpLCAiX3Rlc3QiKQp0cmVlX3Jlc3VsdHMgPC0gY2JpbmQodHJlZV9yZXN1bHRzX3RyYWluLCB0cmVlX3Jlc3VsdHNfdGVzdCkKdHJlZV9yZXN1bHRzIDwtIG5hLm9taXQodHJlZV9yZXN1bHRzKSAlPiUgc2VsZWN0KC1tb2RlbF9pbmRleF90ZXN0KSAlPiUKICBhcnJhbmdlKGRlc2MoZjFfc2NvcmVfdGVzdCksIGRlc2MoQXVDX3Rlc3QpLCBkZXNjKGFjY3VyYWN5X3Rlc3QpLCBkZXNjKGtvaGVua2FwcGFfdGVzdCkpCnRyZWVfcmVzdWx0cwpgYGAKVmlzdWFsaXplIHRoZSByZXN1bHQgaW4gbGluZXBsb3RzLCBlYWNoIGxpbmUgcmVwcmVzZW50cyBvbmUgbW9kZWwKCmBgYHtyfQpldmFsX2xvbmcgPC0gdHJlZV9yZXN1bHRzICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYWxsX29mKG1ldHJpY19vcmRlciksIG5hbWVzX3RvID0gJ21ldHJpY3MnLCB2YWx1ZXNfdG8gPSAndmFsdWVzJykKCmdncGxvdChldmFsX2xvbmcsIGFlcyh4ID0gZmFjdG9yKG1ldHJpY3MsIGxldmVscyA9IG1ldHJpY19vcmRlciksIHkgPSB2YWx1ZXMsIGdyb3VwID0gbW9kZWxfaW5kZXhfdHJhaW4pKSArCmdlb21fbGluZShhZXMoY29sb3IgPSBtb2RlbF9pbmRleF90cmFpbikpICsKZ2VvbV9wb2ludChhZXMoY29sb3IgPSBtb2RlbF9pbmRleF90cmFpbikpICsKdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKwp4bGFiKCIiKSArCmdndGl0bGUoIkRlY2lzaW9uIHRyZWU6IEV2YWx1YXRpb24gd2l0aCAxMC1mb2xkIENyb3NzIFZhbGlkYXRpb24iKQpgYGAKClRoZSBsaW5lcyBoYXZlIHZlcnkgc2ltaWxhciBzaGFwZS4KVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0IGlzIHJlbGF0aXZlbHkgbGFyZ2UsIGJlY2F1c2UgdHJlZS10eXBlIG1vZGVsIGlzIHByb25lIHRvIG92ZXJmaXR0aW5nLgoKQ2hlY2sgdGhlIGNvcnJlc3BvbmRpbmcgaHlwZXJwYXJhbWV0ZXJzIG9mIHRoZSBUT1AgNiBiZXN0IG1vZGVscy4KYGBge3J9CmlkeCA8LSB0cmVlX3Jlc3VsdHNbMTo2LCdtb2RlbF9pbmRleF90cmFpbiddCnRvcF82X3BhcmFtZXRlcnMgPC0gaHlwZXJfZ3JpZFtpZHgsXQp0b3BfNl9wYXJhbWV0ZXJzCmBgYAoKIyMjIyAyLjIuMiBiZXN0IGRlY2lzaW9uIHRyZWUgbW9kZWwgYW5kIGV4cGxhaW4gaXQKClNlbGVjdCB0aGUgbW9kZWwgd2l0aCBpbmRleCAxOSBhcyB0aGUgYmVzdCB0cmVlIG1vZGVsLCBhbmQgZXhwbGFpbiBpdC4KYGBge3J9CmZpbmFsX3RyZWVfbW9kZWwgPC0gdHJlZV9tb2RlbHNfbGlzdFtbMV1dCiMgZXhwbGFpbmVyCmV4cGxhaW5lcl90cmVlIDwtIGV4cGxhaW4oZmluYWxfdHJlZV9tb2RlbCwgZGF0YSA9IGRmX3RyYWluLCB5PWFzLm51bWVyaWMoZGZfdHJhaW4kY2FyZF90eXBlKSwgbGFiZWw9J3RyZWUgbW9kZWwnKQpgYGAKCiMjIyMgMi4yLjMgVG9wLTEwIGZlYXR1cmVzOiBwZXJtdWF0YXRpb24gaW1wb3J0YW5jZQoKYGBge3J9CnNldC5zZWVkKDEyMykKcGZpX3RyZWUgPC0gbW9kZWxfcGFydHMoZXhwbGFpbmVyX3RyZWUsIHR5cGUgPSAidmFyaWFibGVfaW1wb3J0YW5jZSIsIE4gPSAxMDAwKQpwbG90KHBmaV90cmVlWzE6MTEsXSwgc2hvd19ib3hwbG90cyA9IEZBTFNFKSArIGdndGl0bGUoInZhcmlhYmxlX2ltcG9ydGFuY2UiLCAiIikKYGBgCkFsbCB0b3AgMTAgaW1wb3J0YW50IGZlYXR1cmVzIGhhdmUgdmlzaWJsZSBpbmZsdWVuY2Ugb24gdGhlIG1vZGVsIHBlcmZvcm1hbmNlLiBUaGUgc2FkX3RyYW5zIGhhcyBzaWduaWZpY2FudCBkb21pbmFudCBpbmZsdWVuY2UgY29tcGFyaW5nIHRvIG90aGVyIGZlYXR1cmVzLgoKIyMjIyAyLjIuNCBUb3AtMTAgY2xpZW50cyBhbmQgZmVhdHVyZSBjb250cmlidXRpb25zCgpgYGB7cn0KIyMgVG9wLU4gY2xpZW50cwpmaW5hbF90cmVlX3ByZWRpY3Rpb25zX3Rlc3QgPC0gdHJlZV9wcmVkaWN0aW9uc190ZXN0X2xpc3RbW2lkeFsxXV1dIApUb3Bfbl9jbGllbnRzX3RyZWUgPC0gZmluYWxfdHJlZV9wcmVkaWN0aW9uc190ZXN0ICU+JSBhcnJhbmdlKGRlc2MoYnV5ZXJzKSkKVG9wX25fY2xpZW50c190cmVlCmBgYAoKVGhlIHRvcCBjbGllbnRzIGhhdmUgdmVyeSBoaWdoIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzLCBldmVuIHBlcmZlY3QuCgoxMCBmZWF0dXJlcyB0aGF0IGNvbnRyaWJ1dGVkIG1vc3QgdG8gZWFjaCBvZiB0aGUgdG9wLTEwIGNsaWVudHMKYGBge3J9Cm4gPSAxMApwbG90X2xpc3QgPC0gbGlzdCgpCmZvciAoaSBpbiBzZXEoMSwgbikpIHsKICBpZHggPSBUb3Bfbl9jbGllbnRzX3RyZWVbaSwzXQogIGNsaWVudF9pZCA9IFRvcF9uX2NsaWVudHNfdHJlZVtpLDJdCiAgYmRwX3RyZWUgPC0gcHJlZGljdF9wYXJ0cyhleHBsYWluZXJfdHJlZSwgbmV3X29ic2VydmF0aW9uID0gZGZfdGVzdFtpZHgsIF0pCiAgcCA8LSBwbG90KGJkcF90cmVlKSArIGdndGl0bGUocGFzdGUoIkRlY2lzaW9uIHRyZWUgLSBDbGllbnRfaWQgIixjbGllbnRfaWQpKQogIHBsb3RfbGlzdFtbaV1dIDwtIHB9Cgpmb3IgKGkgaW4gMTpuKSB7CiAgcHJpbnQocGxvdF9saXN0W1tpXV0pCn0KYGBgCgpGb3IgZWFjaCBjbGllbnQgb2YgdGhlIHRvcC0xMCwgbWF4aW1hbCA2IGZlYXR1cmVzIGFyZSBjb250cmlidXRpbmcgdG8gdGhlIHByZWRpY3Rpb24gcmVzdWx0LgoKIyMjIyAyLjIuNSBFdmFsdWF0aW9uCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQp0cmVlX3Jlc3VsdCA8LSBtZXRyaWNzX3RhYmxlKGZpbmFsX3RyZWVfcHJlZGljdGlvbnNfdGVzdCRkZWNpc2lvbiwgZGZfdGVzdCRjYXJkX3R5cGUsICdkZWNpc2lvbiB0cmVlJykKbWV0cmljX3Jlc3VsdHMgPC0gcmJpbmQodHJlZV9yZXN1bHQsIG1ldHJpY19yZXN1bHRzKQpwbG90X3JvY19hdWMoZmluYWxfdHJlZV9wcmVkaWN0aW9uc190ZXN0LCAiRGVjaXNpb24gdHJlZTogUk9DIikKYGBgCgpBVUMgPSAwLjg1IGlzIHJlbGF0aXZlbHkgbGFyZ2UsIHRoaXMgbWVhbnMgdGhlIGRlY2lzaW9uIHRyZWUgcHJlZGljdGlvbiBpcyBtdWNoIGJldHRlciB0aGFuIHJhbmRvbSBndWVzc2luZy4KCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpjb25mX21hdHJpeCA8LSBtZXRyaWNzX3RhYmxlKGZpbmFsX3RyZWVfcHJlZGljdGlvbnNfdGVzdCRkZWNpc2lvbiwgZGZfdGVzdCRjYXJkX3R5cGUsICdkZWNpc2lvbiB0cmVlJywgcmV0dXJuX2NvbmZfbWF0cml4PVRSVUUpCmdncGxvdChjb25mX21hdHJpeCwgYWVzKFJlZmVyZW5jZSwgUHJlZGljdGlvbiwgZmlsbD0gRnJlcSkpICsKICAgICAgICBnZW9tX3RpbGUoKSArIGdlb21fdGV4dChhZXMobGFiZWw9RnJlcSkpICsKICAgICAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSIjMDA5MTk0IikgKwogICAgICAgIGxhYnMoeCA9ICJSZWZlcmVuY2UiLHkgPSAiUHJlZGljdGlvbiIsIHRpdGxlID0gIkRlY2lzaW9uIHRyZWU6IENvbmZ1c2lvbiBtYXRyaXgiKSAKYGBgClRoZSBjb25mdXNpb24gbWF0cml4IGluZGljYXRlcywgdGhlIG1vZGVsIHByZWRpY3RzIGJldHRlciBvbiBub25idXllcnMgZ3JvdXAgdGhhbiB0aGUgYnV5ZXJzIGdyb3VwICh3ZWFrIHJlY2FsbCkuCgpgYGB7cn0KbWV0cmljX3Jlc3VsdHMKYGBgCgpUaGUgZmluYWwgZGVjaXNpb24gdHJlZSBtb2RlbCBwZXJmb3JtcyBiZXR0ZXIgdGhhbiB0aGUgYmFzZWxpbmUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4KCiMjIyAyLjMgUmFuZG9tIGZvcmVzdAoKIyMjIyAyLjMuMSB0cmFpbiBtb2RlbCBhbmQgdHVuZSBoeXBlcnBhcmFtZXRlciAKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIERlZmluZSB0aGUgaHlwZXJwYXJhbWV0ZXIgZ3JpZCBmb3IgZ3JpZCBzZWFyY2gKaHlwZXJfZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBudHJlZSA9IGMoNTAsIDEwMCwgNTAwLCAxMDAwKSwgICAjIE51bWJlciBvZiB0cmVlcyBpbiB0aGUgZm9yZXN0LiAgCiAgbXRyeSA9IGMoMiwgNCwgNiksICAgICAgICAgICMgTnVtYmVyIG9mIHZhcmlhYmxlcyByYW5kb21seSBjaG9zZW4gYXQgZWFjaCBzcGxpdCAKICBub2Rlc2l6ZSA9IGMoNSwgMTAsIDIwKSAgICAgIyBNaW5pbXVtIHNpemUgb2YgdGVybWluYWwgbm9kZXMgCikKCmZvcmVzdF9yZXN1bHRzX3RyYWluIDwtIGRhdGEuZnJhbWUoKQpmb3Jlc3RfcmVzdWx0c190ZXN0IDwtIGRhdGEuZnJhbWUoKQpmb3Jlc3RfbW9kZWxzX2xpc3QgPC0gbGlzdCgpCmZvcmVzdF9wcmVkaWN0aW9uc190ZXN0X2xpc3QgPC0gbGlzdCgpCgpmb3IgKGkgaW4gMTpucm93KGh5cGVyX2dyaWQpKSB7CiAgcmZfcGFyYW1zIDwtIGxpc3QoCiAgICBudHJlZSA9IGh5cGVyX2dyaWQkbnRyZWVbaV0sCiAgICBtdHJ5ID0gaHlwZXJfZ3JpZCRtdHJ5W2ldLAogICAgbm9kZXNpemUgPSBoeXBlcl9ncmlkJG5vZGVzaXplW2ldKQogIAogIGN0cmwgPC0gdHJhaW5Db250cm9sKAogICAgbWV0aG9kID0gImN2IiwKICAgIG51bWJlciA9IDEwLCAKICAgIHZlcmJvc2VJdGVyID0gRkFMU0UpCiAgCiAgIyBUcmFpbiBhIFJhbmRvbSBGb3Jlc3QgbW9kZWwgd2l0aCBjcm9zcy12YWxpZGF0aW9uCiAgc2V0LnNlZWQoMTIzKQogIHJmX21vZGVsIDwtIHRyYWluKAogICAgY2FyZF90eXBlIH4gLiwKICAgIGRhdGEgPSBkZl90cmFpbiwKICAgIHdlaWdodHMgPSBjbGFzc193ZWlnaHRzLAogICAgbWV0aG9kID0gInJmIiwKICAgIHRyQ29udHJvbCA9IGN0cmwsCiAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUobXRyeSA9IHJmX3BhcmFtcyRtdHJ5KSwKICAgIG50cmVlID0gcmZfcGFyYW1zJG50cmVlLAogICAgbm9kZXNpemUgPSByZl9wYXJhbXMkbm9kZXNpemUpCgogIGZvcmVzdF9tb2RlbHNfbGlzdFtbaV1dIDwtIHJmX21vZGVsCiAgIyBNYWtlIHByZWRpY3Rpb25zIGFuZCBldmFsdWF0aW9ucyBvbiB0aGUgdHJhaW5pbmcgc2V0CiAgdHJhaW5fcHJlZGljdGlvbnMgPC0gcHJlZGljdChyZl9tb2RlbCwgZGZfdHJhaW4pCiAgcmVzdWx0IDwtIG1ldHJpY3NfdGFibGUodHJhaW5fcHJlZGljdGlvbnMsIGRmX3RyYWluJGNhcmRfdHlwZSwgaSkKICBmb3Jlc3RfcmVzdWx0c190cmFpbiA8LSByYmluZChmb3Jlc3RfcmVzdWx0c190cmFpbiwgcmVzdWx0KQogIAogICMgTWFrZSBwcmVkaWN0aW9ucyBhbmQgZXZhbHVhdGlvbnMgb24gdGhlIHRlc3Qgc2V0CiAgdGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJmX21vZGVsLCBkZl90ZXN0LCB0eXBlID0gInByb2IiKQogIHRlc3RfcHJlZGljdGlvbnMgPC0gY2JpbmQodGVzdF9wcmVkaWN0aW9ucywgZGZfdGVzdF9jbGllbnRpZCkgJT4lIHNlbGVjdChjKGJ1eWVycywgY2xpZW50X2lkKSklPiUgbXV0YXRlKGlkeCA9IDE6bnJvdyh0ZXN0X3ByZWRpY3Rpb25zKSwgZGVjaXNpb24gPSBhcy5mYWN0b3IoaWZlbHNlKGJ1eWVycyA+PSAwLjUsICdidXllcnMnLCAnbm9uYnV5ZXJzJykpKSAKICBmb3Jlc3RfcHJlZGljdGlvbnNfdGVzdF9saXN0W1tpXV0gPC0gdGVzdF9wcmVkaWN0aW9ucwogIAogIHJlc3VsdCA8LSBtZXRyaWNzX3RhYmxlKHRlc3RfcHJlZGljdGlvbnMkZGVjaXNpb24sIGRmX3Rlc3QkY2FyZF90eXBlLCBpKQogIGZvcmVzdF9yZXN1bHRzX3Rlc3QgPC0gcmJpbmQoZm9yZXN0X3Jlc3VsdHNfdGVzdCwgcmVzdWx0KX0KCiMgYmluZCB0cmFpbiBhbmQgdGVzdCBtZXRyaWNzCmNvbG5hbWVzKGZvcmVzdF9yZXN1bHRzX3RyYWluKSA8LSBwYXN0ZTAoY29sbmFtZXMoZm9yZXN0X3Jlc3VsdHNfdHJhaW4pLCAiX3RyYWluIikKY29sbmFtZXMoZm9yZXN0X3Jlc3VsdHNfdGVzdCkgPC0gcGFzdGUwKGNvbG5hbWVzKGZvcmVzdF9yZXN1bHRzX3Rlc3QpLCAiX3Rlc3QiKQpmb3Jlc3RfcmVzdWx0cyA8LSBjYmluZChmb3Jlc3RfcmVzdWx0c190cmFpbiwgZm9yZXN0X3Jlc3VsdHNfdGVzdCkKZm9yZXN0X3Jlc3VsdHMgPC0gbmEub21pdChmb3Jlc3RfcmVzdWx0cykgJT4lIHNlbGVjdCgtbW9kZWxfaW5kZXhfdGVzdCkgJT4lCiAgYXJyYW5nZShkZXNjKGYxX3Njb3JlX3Rlc3QpLCBkZXNjKEF1Q190ZXN0KSwgZGVzYyhhY2N1cmFjeV90ZXN0KSwgZGVzYyhrb2hlbmthcHBhX3Rlc3QpKQpmb3Jlc3RfcmVzdWx0cwpgYGAKCmBgYHtyfQpldmFsX2xvbmcgPC0gZm9yZXN0X3Jlc3VsdHMgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBhbGxfb2YobWV0cmljX29yZGVyKSwgbmFtZXNfdG8gPSAnbWV0cmljcycsIHZhbHVlc190byA9ICd2YWx1ZXMnKQpnZ3Bsb3QoZXZhbF9sb25nLCBhZXMoeCA9IGZhY3RvcihtZXRyaWNzLCBsZXZlbHMgPSBtZXRyaWNfb3JkZXIpLCB5ID0gdmFsdWVzLCBncm91cCA9IG1vZGVsX2luZGV4X3RyYWluKSkgKwpnZW9tX2xpbmUoYWVzKGNvbG9yID0gbW9kZWxfaW5kZXhfdHJhaW4pKSArCmdlb21fcG9pbnQoYWVzKGNvbG9yID0gbW9kZWxfaW5kZXhfdHJhaW4pKSArCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKeGxhYigiIikgKwpnZ3RpdGxlKCJSYW5kb20gZm9yZXN0OiBFdmFsdWF0aW9uIHdpdGggMTAtZm9sZCBDcm9zcyBWYWxpZGF0aW9uIikKYGBgCldlIHNlZSB0d28gZ3JvdXBzIG9mIGxpbmVzIGZyb20gdGhlIHBsb3QuIENvbXBhcmluZyB0byBkZWNpc2lvbiB0cmVlLCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRyYWluIGFuZCB0ZXN0IGRhdGFzZXQgaXMgc21hbGxlci4gVGhpcyBpcyBiZWNhdXNlLCByYW5kb20gZm9yZXN0IHVzZXMgbXVsdGlwbGUgbnVtYmVyIG9mIGRlY2lzaW9uIHRyZWUgbW9kZWxzLCB0byByZWR1Y2Ugb3ZlcmZpdHRpbmcuCgojIyMjIDIuMy4yIGJlc3QgcmFuZG9tIGZvcmVzdCAKCkNoZWNrIHRoZSBleGFjdCBwYXJhbWV0ZXJzIG9mIHRoZSB0b3AgNiBiZXN0IHJhbmRvbSBmb3Jlc3QgbW9kZWxzLgoKYGBge3J9CmlkeCA8LSBmb3Jlc3RfcmVzdWx0c1sxOjYsJ21vZGVsX2luZGV4X3RyYWluJ10KdG9wXzZfcGFyYW1ldGVycyA8LSBoeXBlcl9ncmlkW2lkeCxdCnRvcF82X3BhcmFtZXRlcnMKYGBgCgpCZXN0IHJhbmRvbSBmb3Jlc3QgbW9kZWwKYGBge3J9CmZpbmFsX2ZvcmVzdF9tb2RlbCA8LSBmb3Jlc3RfbW9kZWxzX2xpc3RbWzFdXQojIGV4cGxhaW5lcgpleHBsYWluZXJfZm9yZXN0IDwtIGV4cGxhaW4oZmluYWxfZm9yZXN0X21vZGVsLCBkYXRhID0gZGZfdHJhaW4sIHk9YXMubnVtZXJpYyhkZl90cmFpbiRjYXJkX3R5cGUpLCBsYWJlbD0ncmFuZG9tIGZvcmVzdCBtb2RlbCcpCmBgYAogCiMjIyMgMi4zLjMgVG9wLTEwIGZlYXR1cmVzIGJ5IHBlcm11dGF0aW9uIGltcG9ydGFuY2UKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCnBmaV9mb3Jlc3QgPC0gbW9kZWxfcGFydHMoZXhwbGFpbmVyX2ZvcmVzdCwgdHlwZSA9ICJ2YXJpYWJsZV9pbXBvcnRhbmNlIiwgTiA9IDEwMDApCnBsb3QocGZpX2ZvcmVzdFsxOjExLF0sIHNob3dfYm94cGxvdHMgPSBGQUxTRSkgKyBnZ3RpdGxlKCJ2YXJpYWJsZV9pbXBvcnRhbmNlIiwgIiIpCmBgYApUaGUgdG9wIDEwIGZlYXR1cmVzIGFyZSBtYWlubHkgdGhlIGFjY291bnRfYWdlLCBhbmQgc3RhdGlzdGljcyBmZWF0dXJlcyBvZiB0cmFuc2FjdGlvbiBhbmQgYXNzZXQuClRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlIGFjY291bnRfYWdlIGhhcyBhbiBpbmZsdWVuY2UgZmFjdG9yIG9mIGFyb3VuZCAwLjAwMDguIFRoaXMgaXMgdmVyeSBzbWFsbCBjb21wYXJpbmcgdG8gdXBwZXIgdHdvIG1vZGVscy4gVGhpcyBtZWFucywgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdXNlcyBhIGxvbmcgbGlzdCBvZiBmZWF0dXJlcyBjYW5kaWRhdGVzIHdpdGggc2ltaWxhciBpbXBvcnRhbmNlLCB3aGVyZSB0aGUgZGVjaXNpb24gdHJlZSBhbmQgbG9naXN0aWMgcmVncmVzc2lvbiByZWxpZWQgb24gb25seSBhIGZldyBmZWF0dXJlcy4gCgojIyMjIDIuMy40IFRvcC0xMCBjbGllbnRzCmBgYHtyfQpmaW5hbF9mb3Jlc3RfcHJlZGljdGlvbnNfdGVzdCA8LSBmb3Jlc3RfcHJlZGljdGlvbnNfdGVzdF9saXN0W1tpZHhbMV1dXSAKVG9wX25fY2xpZW50c19mb3Jlc3QgPC0gZmluYWxfZm9yZXN0X3ByZWRpY3Rpb25zX3Rlc3QgJT4lIGFycmFuZ2UoZGVzYyhidXllcnMpKQpUb3Bfbl9jbGllbnRzX2ZvcmVzdApgYGAKClRoZSBwcm9iYWJpbGl0aWVzIGFyZSByZWxhdGl2ZWx5IGdvb2QsIGJ1dCBzbWFsbGVyIHRoYW4gdGhlIGRlY2lzaW9uIHRyZWUuCgpGZWF0dXJlcyBjb250cmlidXRpb24gYW5hbHlzaXMgZm9yIHRvcC0xMCBjbGllbnRzCmBgYHtyfQpuID0gMTAKcGxvdF9saXN0IDwtIGxpc3QoKQpmb3IgKGkgaW4gc2VxKDEsIG4pKSB7CiAgaWR4ID0gVG9wX25fY2xpZW50c19mb3Jlc3RbaSwzXQogIGNsaWVudF9pZCA9IFRvcF9uX2NsaWVudHNfZm9yZXN0W2ksMl0KICBiZHBfZm9yZXN0IDwtIHByZWRpY3RfcGFydHMoZXhwbGFpbmVyX2ZvcmVzdCwgbmV3X29ic2VydmF0aW9uID0gZGZfdGVzdFtpZHgsIF0pCiAgcCA8LSBwbG90KGJkcF9mb3Jlc3QpICsgZ2d0aXRsZShwYXN0ZSgiUmFuZG9tIGZvcmVzdCAtIENsaWVudF9pZCAiLGNsaWVudF9pZCkpCiAgcGxvdF9saXN0W1tpXV0gPC0gcH0KCmZvciAoaSBpbiAxOm4pIHsKICBwcmludChwbG90X2xpc3RbW2ldXSkKfQpgYGAKCkZvciBlYWNoIGNsaWVudCwgdGhlIHRvcCAxMCBjb250cmlidXRlZCBmZWF0dXJlcyBoYXZlIGFjdHVhbGx5IHZlcnkgc2ltaWxhciBzdHJlbmd0aCBvZiBjb250cmlidXRpb24uIFRoZSBhbGwgb3RoZXIgZmVhdHVyZXMgdG9nZXRoZXIgaGF2ZSBhcyB3ZWxsIHNpZ25pZmljYW50IGNvbnRyaWJ1dGlvbi4gVGhpcyB0ZWxscywgdGhlcmUgYXJlIGEgbG9uZyBsaXN0IG9mIGltcG9ydGFudCBmZWF0dXJlcyBjb250cmlidXRlIHRvIHRoZSBmaW5hbCBwcmVkaWN0aW9uIG9mIGVhY2ggY2xpZW50LgoKIyMjIyAyLjMuNSBFdmFsdWF0aW9uCgpBVUMgPSAwLjkxLCBpbmRpY2F0aW5nIGEgZ29vZCBwcmVkaWN0aW9uLgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZm9yZXN0X3Jlc3VsdCA8LSBtZXRyaWNzX3RhYmxlKGZpbmFsX2ZvcmVzdF9wcmVkaWN0aW9uc190ZXN0JGRlY2lzaW9uLCBkZl90ZXN0JGNhcmRfdHlwZSwgJ3JhbmRvbSBmb3Jlc3QnKQptZXRyaWNfcmVzdWx0cyA8LSByYmluZChmb3Jlc3RfcmVzdWx0LCBtZXRyaWNfcmVzdWx0cykKcGxvdF9yb2NfYXVjKGZpbmFsX2ZvcmVzdF9wcmVkaWN0aW9uc190ZXN0LCAiUmFuZG9tIGZvcmVzdDogUk9DIikKYGBgCkNvbmZ1c2lvbiBtYXRyaXgKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmNvbmZfbWF0cml4IDwtIG1ldHJpY3NfdGFibGUoZmluYWxfZm9yZXN0X3ByZWRpY3Rpb25zX3Rlc3QkZGVjaXNpb24sIGRmX3Rlc3QkY2FyZF90eXBlLCAncmFuZG9tIGZvcmVzdCcsIHJldHVybl9jb25mX21hdHJpeD1UUlVFKQpnZ3Bsb3QoY29uZl9tYXRyaXgsIGFlcyhSZWZlcmVuY2UsIFByZWRpY3Rpb24sIGZpbGw9IEZyZXEpKSArCiAgICAgICAgZ2VvbV90aWxlKCkgKyBnZW9tX3RleHQoYWVzKGxhYmVsPUZyZXEpKSArCiAgICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IndoaXRlIiwgaGlnaD0iIzAwOTE5NCIpICsKICAgICAgICBsYWJzKHggPSAiUmVmZXJlbmNlIix5ID0gIlByZWRpY3Rpb24iLCB0aXRsZSA9ICJSYW5kb20gZm9yZXN0OiBDb25mdXNpb24gbWF0cml4IikgCmBgYApjb25mdXNpb24gbWF0cml4OiBUaGUgcmFuZG9tIGZvcmVzdCBjb3VsZCBwZXJmZWN0bHkgcHJlZGljdCB0aGUgbm9uYnV5ZXJzIGdyb3VwLCBidXQgc3RpbGwgb3ZlciBoYWxmIG9mIHRoZSBidXllcnMgYXJlIGZhbHNlIHByZWRpY3RlZCBhcyBub25idXllcnMuIFRoaXMgbWVhbnMsIHRob3VnaCB3aXRoIGNsYXNzX3dlaWdodHMsIHRoZSByYW5kb20gZm9yZXN0IGNvdWxkIG5vdCBwZXJmb3JtIHRoYXQgd2VsbCBvbiB0aGUgbWlub3IgZ3JvdXAuCgpgYGB7cn0KbWV0cmljX3Jlc3VsdHMKYGBgCgpPdmVyIHRoZSBldmFsdWF0aW9ucyBvZiBhbGwgc2l4IG1ldHJpY3Mgb24gdGVzdHNldCwgcmFuZG9tIGZvcmVzdCBoYXMgbXVjaCBiZXR0ZXIgcGVyZm9ybWFuY2UgdGhhbiBkZWNpc2lvbiB0cmVlIGFuZCBsb2dpc3RpYyByZWdyZXNzaW9uLgoKCgojIyMgMi40IFN1cHBvcnQgdmVjdG9yIG1hY2hpbmUgKFNWTSkKCiMjIyMgMi40LjEgdHJhaW4gYW5kIHR1bmUgaHlwZXJwYXJhbWV0ZXIKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZTEwNzEpICAjIExvYWQgdGhlICdlMTA3MScgcGFja2FnZSBmb3IgU1ZNCgojIERlZmluZSB0aGUgaHlwZXJwYXJhbWV0ZXIgZ3JpZCBmb3IgZ3JpZCBzZWFyY2gKaHlwZXJfZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBDID0gYygwLjAxLCAwLjEsIDEsIDEwKSwKICBrZXJuZWwgPSBjKCJsaW5lYXIiLCAicmFkaWFsIiwgInBvbHlub21pYWwiLCAic2lnbW9pZCIpCikKCnN2bV9yZXN1bHRzX3RyYWluIDwtIGRhdGEuZnJhbWUoKQpzdm1fcmVzdWx0c190ZXN0IDwtIGRhdGEuZnJhbWUoKQpzdm1fbW9kZWxzX2xpc3QgPC0gbGlzdCgpCnN2bV9wcmVkaWN0aW9uc190ZXN0X2xpc3QgPC0gbGlzdCgpCiMgUGVyZm9ybSBncmlkIHNlYXJjaCBmb3IgU1ZNCmZvciAoaSBpbiAxOm5yb3coaHlwZXJfZ3JpZCkpIHsKICBzdm1fbW9kZWwgPC0gc3ZtKAogICAgY2FyZF90eXBlIH4gLiwKICAgIGRhdGEgPSBkZl90cmFpbiwKICAgIHRyQ29udHJvbCA9IGN0cmwsCiAgICB3ZWlnaHRzID0gY2xhc3Nfd2VpZ2h0cywKICAgIHR5cGUgPSAiQy1jbGFzc2lmaWNhdGlvbiIsCiAgICBrZXJuZWwgPSBoeXBlcl9ncmlkJGtlcm5lbFtpXSwKICAgIGNvc3QgPSBoeXBlcl9ncmlkJENbaV0sCiAgICBzY2FsZSA9IFRSVUUsICAjIFNjYWxlIHRoZSBkYXRhIGZvciBiZXR0ZXIgcGVyZm9ybWFuY2UKICAgIHByb2JhYmlsaXR5ID0gVFJVRSkKICBzdm1fbW9kZWxzX2xpc3RbW2ldXSA8LSBzdm1fbW9kZWwKICAKICAjIEV2YWx1YXRlIG1vZGVsIHBlcmZvcm1hbmNlCiAgdHJhaW5fcHJlZGljdGlvbnMgPC0gcHJlZGljdChzdm1fbW9kZWwsIGRmX3RyYWluKQogIHJlc3VsdCA8LSBtZXRyaWNzX3RhYmxlKHRyYWluX3ByZWRpY3Rpb25zLCBkZl90cmFpbiRjYXJkX3R5cGUsIGkpCiAgc3ZtX3Jlc3VsdHNfdHJhaW4gPC0gcmJpbmQoc3ZtX3Jlc3VsdHNfdHJhaW4sIHJlc3VsdCkKICAKICAjIEV2YWx1YXRlIG1vZGVsIG9uIHRlc3RzZXQKICB0ZXN0X3ByZWRpY3Rpb25zIDwtIGF0dHIocHJlZGljdChzdm1fbW9kZWwsIGRmX3Rlc3QsIHByb2JhYmlsaXR5PVRSVUUpLCAicHJvYmFiaWxpdGllcyIpCiAgdGVzdF9wcmVkaWN0aW9ucyA8LSBjYmluZCh0ZXN0X3ByZWRpY3Rpb25zLCBkZl90ZXN0X2NsaWVudGlkKSAlPiUgc2VsZWN0KGMoYnV5ZXJzLCBjbGllbnRfaWQpKSU+JSBtdXRhdGUoaWR4ID0gMTpucm93KHRlc3RfcHJlZGljdGlvbnMpLCBkZWNpc2lvbiA9IGFzLmZhY3RvcihpZmVsc2UoYnV5ZXJzID49IDAuNSwgJ2J1eWVycycsICdub25idXllcnMnKSkpIAogIHN2bV9wcmVkaWN0aW9uc190ZXN0X2xpc3RbW2ldXSA8LSB0ZXN0X3ByZWRpY3Rpb25zCiAgcmVzdWx0ID0gbWV0cmljc190YWJsZSh0ZXN0X3ByZWRpY3Rpb25zJGRlY2lzaW9uLCBkZl90ZXN0JGNhcmRfdHlwZSwgaSkKICBzdm1fcmVzdWx0c190ZXN0IDwtIHJiaW5kKHN2bV9yZXN1bHRzX3Rlc3QsIHJlc3VsdCkKfQogIAojIGJpbmQgdHJhaW4gYW5kIHRlc3QgbWV0cmljcwpjb2xuYW1lcyhzdm1fcmVzdWx0c190cmFpbikgPC0gcGFzdGUwKGNvbG5hbWVzKHN2bV9yZXN1bHRzX3RyYWluKSwgIl90cmFpbiIpCmNvbG5hbWVzKHN2bV9yZXN1bHRzX3Rlc3QpIDwtIHBhc3RlMChjb2xuYW1lcyhzdm1fcmVzdWx0c190ZXN0KSwgIl90ZXN0IikKc3ZtX3Jlc3VsdHMgPC0gY2JpbmQoc3ZtX3Jlc3VsdHNfdHJhaW4sIHN2bV9yZXN1bHRzX3Rlc3QpCnN2bV9yZXN1bHRzIDwtIG5hLm9taXQoc3ZtX3Jlc3VsdHMpICU+JSBzZWxlY3QoLW1vZGVsX2luZGV4X3Rlc3QpICU+JQogIGFycmFuZ2UoZGVzYyhmMV9zY29yZV90ZXN0KSwgZGVzYyhBdUNfdGVzdCksIGRlc2MoYWNjdXJhY3lfdGVzdCksIGRlc2Moa29oZW5rYXBwYV90ZXN0KSkKc3ZtX3Jlc3VsdHMKYGBgClZpc3VhbGl6ZSB0aGUgZXZhbHVhdGlvbiByZXN1bHQgb2YgdHVuaW5nIHJlc3VsdHMuCmBgYHtyfQpldmFsX2xvbmcgPC0gc3ZtX3Jlc3VsdHMgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBhbGxfb2YobWV0cmljX29yZGVyKSwgbmFtZXNfdG8gPSAnbWV0cmljcycsIHZhbHVlc190byA9ICd2YWx1ZXMnKQoKZ2dwbG90KGV2YWxfbG9uZywgYWVzKHggPSBmYWN0b3IobWV0cmljcywgbGV2ZWxzID0gbWV0cmljX29yZGVyKSwgeSA9IHZhbHVlcywgZ3JvdXAgPSBtb2RlbF9pbmRleF90cmFpbikpICsKZ2VvbV9saW5lKGFlcyhjb2xvciA9IG1vZGVsX2luZGV4X3RyYWluKSkgKwpnZW9tX3BvaW50KGFlcyhjb2xvciA9IG1vZGVsX2luZGV4X3RyYWluKSkgKwp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArCnhsYWIoIiIpICsKZ2d0aXRsZSgiU1ZNOiBFdmFsdWF0aW9uIHdpdGggMTAtZm9sZCBDcm9zcyBWYWxpZGF0aW9uIikKYGBgCgpBIGZldyBtb2RlbHMgaGF2ZSBsYXJnZXIgbWV0cmljIHZhbHVlcyBvbiB0ZXN0c2V0IHRoYW4gb24gdHJhaW5zZXQsIHdoaWNoIGlzIGEgc2lnbmFsIG9mIHVuZGVyZml0dGluZy4KVGhlcmUgaXMgb25lIG1vZGVsIHdpdGggc2lnbmlmaWNhbnQgaGlnaGVyIHZhbHVlcyBvZiBhbGwgbWV0cmljcyB0aGFuIG90aGVycyBhbmQgd2l0aG91dCB1bmRlcmZpdHRpbmcgc2lnbi4KVGhpcyBzaG91bGQgYmUgdGhlIGJlc3QgbW9kZWwuIEJ1dCBzdGlsbCBJIHdpbGwgY2hlY2sgdGhlIGh5cGVycGFyYW1ldGVycyBvZiB0b3AgNiBiZXN0IG1vZGVscy4KCiMjIyMgMi40LjIgYmVzdCBtb2RlbAoKRmluZCB0aGUgY29ycmVzcG9uZGluZyBoeXBlcnBhcmFtZXRlcnMgb2YgdGhlIFRPUCA2IGJlc3QgbW9kZWxzLgpgYGB7cn0KaWR4IDwtIHN2bV9yZXN1bHRzWzE6NiwnbW9kZWxfaW5kZXhfdHJhaW4nXQp0b3BfNl9wYXJhbWV0ZXJzIDwtIGh5cGVyX2dyaWRbaWR4LF0KdG9wXzZfcGFyYW1ldGVycwpgYGAKCmJlc3QgU1ZNIG1vZGVsIGFuZCBleHBsYWluIGl0CmBgYHtyfQpmaW5hbF9zdm1fbW9kZWwgPC0gc3ZtX21vZGVsc19saXN0W1sxXV0KIyBleHBsYWluZXIKZXhwbGFpbmVyX3N2bSA8LSBleHBsYWluKGZpbmFsX3N2bV9tb2RlbCwgZGF0YSA9IGRmX3RyYWluLCB5PWFzLm51bWVyaWMoZGZfdHJhaW4kY2FyZF90eXBlKSwgbGFiZWw9J3N2bSBtb2RlbCcpCmBgYAoKIyMjIyAyLjQuMyBUb3AtMTAgaW1wb3J0YW50IGZlYXR1cmVzCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpwZmlfc3ZtIDwtIG1vZGVsX3BhcnRzKGV4cGxhaW5lcl9zdm0sIHR5cGUgPSAidmFyaWFibGVfaW1wb3J0YW5jZSIsIE4gPSAxMDAwKQpwbG90KHBmaV9zdm1bMToxMSxdLCBzaG93X2JveHBsb3RzID0gRkFMU0UpICsgZ2d0aXRsZSgiU1ZNOiBUb3AgMTAgaW1wb3J0YW50IGZlYXR1cmVzIikKYGBgCgpUaGUgdG9wIDEwIGltcG9ydGFudCBmZWF0dXJlcyBpbiBTVk0gbW9kZWwgYXJlIHRoZSBzdGF0aXN0aWNzIG9mIHRyYW5zYWN0aW9uIGFuZCBhc3NldC4KVGhlIGltcG9ydGFuY2UgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0b3AgMTAgZmVhdHVyZXMgYXJlIG1pbGQsIGJ1dCBhbGwgYXJlIHNpZ25pZmljYW50LgpUaGUgdG9wIDEgZmVhdHVyZSBtYXhfdHJhbnMgY2FuIGluZmx1ZW5jZSBtb2RlbCBwZXJmb3JtYW5jZSB3aXRoIGFsbW9zdCAwLjEsIGFyb3VuZCB0aHJlZSB0aW1lcyBsYXJnZSBhcyB0aGUgdG9wIDEwIGZlYXR1cmUgdmFyX2Fzc2V0LgoKCiMjIyMgMi40LjQgVG9wLTEwIGNsaWVudHMKYGBge3J9CmZpbmFsX3N2bV9wcmVkaWN0aW9uc190ZXN0IDwtIHN2bV9wcmVkaWN0aW9uc190ZXN0X2xpc3RbW2lkeFsxXV1dIApUb3Bfbl9jbGllbnRzX3N2bSA8LSBmaW5hbF9zdm1fcHJlZGljdGlvbnNfdGVzdCAlPiUgYXJyYW5nZShkZXNjKGJ1eWVycykpClRvcF9uX2NsaWVudHNfc3ZtCmBgYAoKVGhlIHByZWRpY3Rpb25zIG9mIHRvcCBjbGllbnRzIGFyZSB2ZXJ5IGhpZ2ggYW5kIHNpbWlsYXIsIGFsbW9zdCBwZXJmZWN0LgoKIyMjIyAyLjQuNSBFdmFsdWF0aW9uCgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0Kc3ZtX3Jlc3VsdCA8LSBtZXRyaWNzX3RhYmxlKGZpbmFsX3N2bV9wcmVkaWN0aW9uc190ZXN0JGRlY2lzaW9uLCBkZl90ZXN0JGNhcmRfdHlwZSwgJ1NWTScpCm1ldHJpY19yZXN1bHRzIDwtIHJiaW5kKHN2bV9yZXN1bHQsIG1ldHJpY19yZXN1bHRzKQpwbG90X3JvY19hdWMoZmluYWxfZm9yZXN0X3ByZWRpY3Rpb25zX3Rlc3QsICJTVk06IFJPQyIpCmBgYApST0MgZ3JhcGggaW5kaWNhdGluZyBhIGdvb2QgcHJlZGljdGlvbiBhcyB3ZWxsLgoKClBsb3QgY29uZnVzaW9uIG1hdHJpeApgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KY29uZl9tYXRyaXggPC0gbWV0cmljc190YWJsZShmaW5hbF9zdm1fcHJlZGljdGlvbnNfdGVzdCRkZWNpc2lvbiwgZGZfdGVzdCRjYXJkX3R5cGUsICdTVk0nLCByZXR1cm5fY29uZl9tYXRyaXg9VFJVRSkKZ2dwbG90KGNvbmZfbWF0cml4LCBhZXMoUmVmZXJlbmNlLCBQcmVkaWN0aW9uLCBmaWxsPSBGcmVxKSkgKwogICAgICAgIGdlb21fdGlsZSgpICsgZ2VvbV90ZXh0KGFlcyhsYWJlbD1GcmVxKSkgKwogICAgICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJ3aGl0ZSIsIGhpZ2g9IiMwMDkxOTQiKSArCiAgICAgICAgbGFicyh4ID0gIlJlZmVyZW5jZSIseSA9ICJQcmVkaWN0aW9uIiwgdGl0bGUgPSAiU1ZNOiBDb25mdXNpb24gbWF0cml4IikgCmBgYApUaGUgU1ZNIG1hZGUgbXVjaCBiZXR0ZXIgcHJlZGljdGlvbiBvbiBidXllcnMgZ3JvdXAgdGhhbiBhbGwgb3RoZXIgdGhyZWUgbW9kZWxzLiBUaGlzIG1lYW5zLCBTVk0gY291bGQgYmV0dGVyIGhhbmRsZSBpbWJhbGFuY2VkIGRhdGEuCgojIyAzIENvbXBhcmUgdGhlIGZvdXIgbW9kZWxzIGFuZCBpZGVudGlmeSB0aGUg4oCcYmVzdOKAnSBtb2RlbAoKIyMjIDMuMSBDb21wYXJlIHRoZSBtb2RlbCBwZXJmb3JtYW5jZQpgYGB7cn0KbWV0cmljX3Jlc3VsdHMKYGBgCgpgYGB7cn0Kb3JkZXIgPC0gYygiZjFfc2NvcmUiLCJBdUMiLCAiYWNjdXJhY3kiLCJrb2hlbmthcHBhIiwicHJlY2lzaW9uIiwicmVjYWxsIikKbWV0cmljX3Jlc3VsdHMkQXVDIDwtIGFzLm51bWVyaWMobWV0cmljX3Jlc3VsdHMkQXVDKQpldmFsX2xvbmcgPC0gbWV0cmljX3Jlc3VsdHMgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBhbGxfb2Yob3JkZXIpLCBuYW1lc190byA9ICdtZXRyaWNzJywgdmFsdWVzX3RvID0gJ3ZhbHVlcycpCgpnZ3Bsb3QoZXZhbF9sb25nLCBhZXMoeCA9IGZhY3RvcihtZXRyaWNzLCBsZXZlbHMgPSBvcmRlciksIHkgPSB2YWx1ZXMsIGdyb3VwID0gbW9kZWxfaW5kZXgpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IG1vZGVsX2luZGV4KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gbW9kZWxfaW5kZXgpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKwogIHhsYWIoIiIpICsKICBnZ3RpdGxlKCJDb21wYXJpc29uIG9mIGZvdXIgbW9kZWxzIikKYGBgCkNvbXBhcmUgdGhlIGV2YWx1YXRpb24gbWV0cmljcyBvZiBmb3VyIG1vZGVsczoKLSAgIFRoZSBwZXJmb3JtYW5jZSByYW5raW5nOiBTVk0gPiByYW5kb20gZm9yZXN0ID4gZGVjaXNpb24gdHJlZSA+PiBsb2dpc3RpYyByZWdyZXNzaW9uCi0gICBUaGUgU1ZNIChTdXBwb3J0IFZlY3RvciBNYWNoaW5lKSBtb2RlbCBoYXMgdGhlIGJlc3QgcGVyZm9ybWFuY2UgY29tcGFyaW5nIHRvIG90aGVyIHRocmVlIG1vZGVscywgYXMgaXQgaGFzIHRoZSBoaWdoZXN0IEYxIHNjb3JlLCBhY2N1cmFjeSwgQXVDLCBDb2hlbidzIGthcHBhLCByZWNhbGwsIGFuZCBzZWNvbmQgaGlnaGVzdCBpbiBwcmVjaXNpb24uIFRoaXMgcG9zc2libHkgZHVlIHRvIHRoZSBTVk0gc3RyZW5ndGhzOiBlZmZlY3RpdmUgaW4gaGlnaCBkaW1lbnNpb24gc3BhY2UsIGxlc3MgcHJvbmUgdG8gb3ZlcmZpdHRpbmcsIGNhbiBoYW5kbGUgaW1iYWxhbmNlZCBkYXRhLgotICAgVGhlIHJhbmRvbSBmb3Jlc3QgaGFzIGFzIHdlbGwgdmVyeSBnb29kIHBlcmZvcm1hbmNlLCBpdCBpcyBvbmx5IHNsaWdodGx5IHdvcnNlIHRoYW4gU1ZNLgotICAgRGVjaXNpb24gdHJlZSdzIHBlcmZvcm1hbmNlIGlzIHdvcnNlIHRoYW4gcmFuZG9tIGZvcmVzdCwgYXMgYSBzaW5nbGUgZGVjaXNpb24gdHJlZSBpcyBwcm9uZSB0byBvdmVyZml0dGluZywgZXZlbiB0aG91Z2ggaXQgcGVyZm9ybXMgd2VsbCBvbiB0cmFpbnNldCwgdXN1YWxseSBoYXJkIHRvIGdlbmVyYWxpemUgd2VsbCBvbiB0ZXN0c2V0LiBSYW5kb20gZm9yZXN0cyByZWR1Y2UgdmFyaWFuY2UgYnkgYXZlcmFnaW5nIG11bHRpcGxlIGRlZXAgZGVjaXNpb24gdHJlZXMsIGVhY2ggdHJhaW5lZCBvbiBhIGRpZmZlcmVudCBwYXJ0IG9mIHRoZSBzYW1lIHRyYWluaW5nIHNldC4gVGhpcyByZXN1bHRzIGluIGEgbW9yZSByb2J1c3QgbW9kZWwgdGhhdCBnZW5lcmFsaXplcyBiZXR0ZXIgdG8gbmV3IGRhdGEgY29tcGFyZWQgdG8gYSBzaW5nbGUgZGVjaXNpb24gdHJlZS4KLSAgIFRoZSBsb2dpc3RpYyBoYXMgbXVjaCB3b3JzZSBwZXJmb3JtYW5jZSBjb21wYXJpbmcgdG8gb3RoZXIgdGhyZWUgbW9kZWxzLiBUaGUgcmVhc29ucyBjb3VsZCBiZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscyBkYXRhIHdpdGggbGluZWFyIGRlY2lzaW9uIGJvdW5kYXJ5LiBCdXQgaW4gdGhpcyBkYXRhc2V0LCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGRlcGVuZGVudCB2YXJpYWJsZSBhbmQgaW5kZXBlbmRlbnQgdmFyaWFibGVzIGlzIG5vdCBsaW5lYXIgYnV0IHJhdGhlciBtb3JlIGNvbXBsZXggaW50ZXJhY3RlZC4KCiMjIyAzLjIgQ29tcGFyZSB0aGUgdG9wIDUlIGFuZCAxMCUgY2xpZW50cyBmb3IgdGhlIGJhc2VsaW5lIGFuZCAKCmBgYHtyfQplbmRfaW5kZXhfNSA8LSBhcy5pbnRlZ2VyKG5yb3coZGZfdGVzdCkqMC4wNSkKZW5kX2luZGV4XzEwIDwtIGFzLmludGVnZXIobnJvdyhkZl90ZXN0KSowLjEpClRvcF81X2NsaWVudHNfbHIgPC0gVG9wX25fY2xpZW50c19sclsxOmVuZF9pbmRleF81LF0kY2xpZW50X2lkClRvcF8xMF9jbGllbnRzX2xyIDwtIFRvcF9uX2NsaWVudHNfbHJbMTplbmRfaW5kZXhfMTAsXSRjbGllbnRfaWQKVG9wXzVfY2xpZW50c190cmVlIDwtIFRvcF9uX2NsaWVudHNfdHJlZVsxOmVuZF9pbmRleF81LF0kY2xpZW50X2lkClRvcF8xMF9jbGllbnRzX3RyZWUgPC0gVG9wX25fY2xpZW50c190cmVlWzE6ZW5kX2luZGV4XzEwLF0kY2xpZW50X2lkClRvcF81X2NsaWVudHNfZm9yZXN0IDwtIFRvcF9uX2NsaWVudHNfZm9yZXN0WzE6ZW5kX2luZGV4XzUsXSRjbGllbnRfaWQKVG9wXzEwX2NsaWVudHNfZm9yZXN0IDwtIFRvcF9uX2NsaWVudHNfZm9yZXN0WzE6ZW5kX2luZGV4XzEwLF0kY2xpZW50X2lkClRvcF81X2NsaWVudHNfc3ZtIDwtIFRvcF9uX2NsaWVudHNfc3ZtWzE6ZW5kX2luZGV4XzUsXSRjbGllbnRfaWQKVG9wXzEwX2NsaWVudHNfc3ZtPC0gVG9wX25fY2xpZW50c19zdm1bMTplbmRfaW5kZXhfMTAsXSRjbGllbnRfaWQKCnRvcDVfbGlzdHM8LSBjKCJUb3BfNV9jbGllbnRzX2xyIiwiVG9wXzVfY2xpZW50c190cmVlIiwiVG9wXzVfY2xpZW50c19mb3Jlc3QiLCJUb3BfNV9jbGllbnRzX3N2bSIpCnRvcDEwX2xpc3RzPC0gYygiVG9wXzEwX2NsaWVudHNfbHIiLCJUb3BfMTBfY2xpZW50c190cmVlIiwiVG9wXzEwX2NsaWVudHNfZm9yZXN0IiwiVG9wXzEwX2NsaWVudHNfc3ZtIikKYGBgCgoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIG92ZXJsYXAgcGVyY2VudGFnZQpjYWxjdWxhdGVfb3ZlcmxhcCA8LSBmdW5jdGlvbihsaXN0MSwgbGlzdDIpIHsKICBjb21tb24gPC0gbGVuZ3RoKGludGVyc2VjdChsaXN0MSwgbGlzdDIpKQogIHVuaXF1ZV90b3RhbCA8LSBsZW5ndGgodW5pcXVlKGMobGlzdDEsIGxpc3QyKSkpCiAgcmV0dXJuIChjb21tb24gLyB1bmlxdWVfdG90YWwgKiAxMDApCn0KCnRvcDVfb3ZlcmxhcF9tYXRyaXggPC0gbWF0cml4KG5yb3cgPSA0LCBuY29sID0gNCkKdG9wMTBfb3ZlcmxhcF9tYXRyaXggPC0gbWF0cml4KG5yb3cgPSA0LCBuY29sID0gNCkKCmZvciAoaSBpbiAxOjQpIHsKICBmb3IgKGogaW4gMTo0KSB7CiAgICBpZiAoaSA9PSBqKSB7CiAgICAgIHRvcDVfb3ZlcmxhcF9tYXRyaXhbaSwgal0gPC0gMTAwCiAgICAgIHRvcDEwX292ZXJsYXBfbWF0cml4W2ksIGpdIDwtIDEwMAogICAgfSBlbHNlIHsKICAgICAgdG9wNV9vdmVybGFwX21hdHJpeFtpLCBqXSA8LSBjYWxjdWxhdGVfb3ZlcmxhcChnZXQodG9wNV9saXN0c1tpXSksIGdldCh0b3A1X2xpc3RzW2pdKSkKICAgICAgdG9wMTBfb3ZlcmxhcF9tYXRyaXhbaSwgal0gPC0gY2FsY3VsYXRlX292ZXJsYXAoZ2V0KHRvcDEwX2xpc3RzW2ldKSwgZ2V0KHRvcDEwX2xpc3RzW2pdKSkKICAgIH19fQoKCnJvd25hbWVzKHRvcDVfb3ZlcmxhcF9tYXRyaXgpIDwtIGNvbG5hbWVzKHRvcDVfb3ZlcmxhcF9tYXRyaXgpIDwtIGMoImxvZ2lzdGljIHJlZ3Jlc3Npb24iLCAiZGVjaXNpb24gdHJlZSIsICJyYW5kb20gZm9yZXN0IiwgIlNWTSIpCnJvd25hbWVzKHRvcDEwX292ZXJsYXBfbWF0cml4KSA8LSBjb2xuYW1lcyh0b3AxMF9vdmVybGFwX21hdHJpeCkgPC0gYygibG9naXN0aWMgcmVncmVzc2lvbiIsICJkZWNpc2lvbiB0cmVlIiwgInJhbmRvbSBmb3Jlc3QiLCAiU1ZNIikKCmxpYnJhcnkocmVzaGFwZTIpCiMgaGVhdG1hcCBvZiBvdmVybGFwcGluZyBvZiB0b3AgNSUgY2xpZW50cwpvdmVybGFwXzUgPC0gYXMuZGF0YS5mcmFtZSh0b3A1X292ZXJsYXBfbWF0cml4KQpvdmVybGFwXzUkTW9kZWwxIDwtIHJvd25hbWVzKG92ZXJsYXBfNSkKb3ZlcmxhcF81IDwtIG92ZXJsYXBfNSAlPiUgZ2F0aGVyKGtleT0nTW9kZWwyJywgdmFsdWU9J092ZXJsYXAnLCAtTW9kZWwxKQoKcDEgPC0gZ2dwbG90KG92ZXJsYXBfNSwgYWVzKHggPSBNb2RlbDEsIHkgPSBNb2RlbDIsIGZpbGwgPSBPdmVybGFwKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJS4xZiUlIiwgT3ZlcmxhcCkpLCB2anVzdCA9IDEpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ5ZWxsb3ciLCBoaWdoID0gInJlZCIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGdndGl0bGUoIlRvcCA1JSBjbGllbnRzOiBPdmVybGFwIEhlYXRtYXAiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDAuNSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCgojIGhlYXRtYXAgb2Ygb3ZlcmxhcHBpbmcgb2YgdG9wIDEwJSBjbGllbnRzCm92ZXJsYXBfMTAgPC0gYXMuZGF0YS5mcmFtZSh0b3AxMF9vdmVybGFwX21hdHJpeCkKb3ZlcmxhcF8xMCRNb2RlbDEgPC0gcm93bmFtZXMob3ZlcmxhcF8xMCkKb3ZlcmxhcF8xMCA8LSBvdmVybGFwXzEwICU+JSBnYXRoZXIoa2V5PSdNb2RlbDInLCB2YWx1ZT0nT3ZlcmxhcCcsIC1Nb2RlbDEpCgpwMiA8LSBnZ3Bsb3Qob3ZlcmxhcF8xMCwgYWVzKHggPSBNb2RlbDEsIHkgPSBNb2RlbDIsIGZpbGwgPSBPdmVybGFwKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJS4xZiUlIiwgT3ZlcmxhcCkpLCB2anVzdCA9IDEpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ5ZWxsb3ciLCBoaWdoID0gInJlZCIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGdndGl0bGUoIlRvcCAxMCUgY2xpZW50czogT3ZlcmxhcCBIZWF0bWFwIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAwLjUpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKQoKZ3JpZC5hcnJhbmdlKHAxLHAyLG5yb3c9MixuY29sPTEpCmBgYAotICAgVGhlIG92ZXJsYXAgcGVyY2VudGFnZSBvZiB0b3AgMTAlIGNsaWVudHMgbGlzdHMgYmV0d2VlbiBldmVyeSB0d28gbW9kZWxzIGFyZSBvdmVyYWxsIGxhcmdlciB0aGFuIHRvcCA1JSBjbGllbnRzLgotICAgVGhlIG92ZXJsYXAgY2xpZW50cyBwZXJjZW50YWdlIGJldHdlZW4gbW9kZWxzIFNWTSBhbmQgcmFuZG9tIGZvcmVzdCBpcyBsYXJnZXN0IGluIGJvdGggaGVhdG1hcHMsIGl0IGV2ZW4gcmVhY2hlcyA2MS44JSBpbiB0b3AgMTAlIGxpc3RzLiBUaGlzIG1lYW5zLCB0aGUgdHdvIG1vZGVscyBzaGFyZSBhIG1vcmUgc2ltaWxhciB2aWV3IG9uIHdobyBhcmUgdGhlIHRvcCBjbGllbnRzLgotICAgVGhlIGRlY2lzaW9uIGhhcyBzbGlnaHRseSBsb3dlciBvdmVybGFwcyB3aXRoIHJhbmRvbSBmb3Jlc3QgYW5kIFNWTS4gCi0gICBIb3dldmVyLCB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBoYXMgb3ZlcmFsbCB2ZXJ5IGxvdyBvdmVybGFwcGluZyBjbGllbnRzIHdpdGggYWxsIG90aGVyIHRocmVlIG1vZGVscy4gVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgZmVhdHVyZXMgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdXNlcyBmb3IgaWRlbnRpZmluZyB0b3AgY2xpZW50cyBhcmUgcXVpdGUgZGlmZmVyZW50IHRvIGRlY2lzaW9uIHRyZWUsIHJhbmRvbSBmb3Jlc3QsIFNWTSBtb2RlbHMuCgoKQmFzZWQgb24gdGhlIHBlcmZvcm1hbmNlIGFuZCB0b3AgY2xpZW50cyBvdmVybGFwcywgSSB3aWxsIGNob29zZSB0aGUgKipTVk0gbW9kZWwqKiBhcyBteSBmaW5hbCBiZXN0IG1vZGVsLCBhcyAKLSAgIGl0IGhhcyB0aGUgaGlnaGVzdCBmMS1zY29yZSwgYWNjdXJheSwgQXVDLCByZWNhbGwsIGFuZCBzZWNvbmQgaGlnaGVzdCBwcmVjaXNpb24gb24gdW5zZWVuIG5ldyB0ZXN0IGRhdGEuCi0gICBpdHMgcHJlZGljdGVkIHRvcCBjbGllbnRzIGFyZSBoaWdobHkgb3ZlcmxhcHBlZCB3aXRoIHJhbmRvbSBmb3Jlc3QgYW5kIGRlY2lzaW9uIHRyZWUgbW9kZWxzLCB0aGlzIHRlbGxzLCBpdHMgcHJlZGljdGlvbnMgY3JpdGVyaWFzIGZvciBpZGVudGlmeWluZyB0b3AgY2xpZW50cyBhcmUgdmVyeSBsaWtlbHkgcmVsaWFibGUuCgoKIyMgNCBHbG9iYWwgaW1wb3J0YW5jZSBvZiB0aGUg4oCcYmVzdOKAnSBtb2RlbCBhbmQgcmVkdWNlIHRoZSDigJxiZXN04oCdIG1vZGVsIGJ5IGJhbGFuY2luZyBnbG9iYWwgaW1wb3J0YW5jZSBhbmQgbW9kZWwgcGVyZm9ybWFuY2UuCgojIyMgNC4xIFRPUC0xMCBwZXJtdWF0YXRpb24gaW1wb3J0YW5jZQoKYGBge3J9CnNldC5zZWVkKDEyMykKcGZpX3N2bSA8LSBtb2RlbF9wYXJ0cyhleHBsYWluZXJfc3ZtLCB0eXBlID0gInZhcmlhYmxlX2ltcG9ydGFuY2UiLCBOID0gMTAwMCkKcGxvdChwZmlfc3ZtWzE6MTEsXSwgc2hvd19ib3hwbG90cyA9IEZBTFNFKSArIGdndGl0bGUoIlNWTTogVG9wIDEwIGltcG9ydGFudCBmZWF0dXJlcyIpCmBgYAoKbWF4X3RyYW5zIGFwcGVhcnMgdG8gYmUgdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmUsIGZvbGxvd2VkIGJ5IHNhZF90cmFucywgc2FkX2Fzc2V0LCBzbG9wZV90cmFucywgdXBwZXJfcXVhcnRpbGVfdHJhbnMsIGFzIHRoZSB0b3AtNS4gSXQgc2VlbXMgdHJhbnNhY3Rpb24sIGFzc2V0LCBhbmQgYWNjb3VudCBhZ2UgYXJlIHZlcnkgaW1wb3J0YW50IGZvciBjbGFzc2lmeWluZyBidXllcnMgYW5kIG5vbmJ1eWVycyBpbiB0aGlzIGNhc2UuCgojIyMgNC4yIHN0ZXB3aXNlIGZlYXR1cmUgc2VsZWN0aW9uCgpHcmFkdWFsbHkgcmVkdWNlIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgYmFzZWQgb24gdGhlaXIgaW1wb3J0YW5jZS4KRmlyc3RseSwgZ2V0IHRoZSBmZWF0dXJlIG5hbWVzIHNvcnRlZCBieSBpbXBvcnRhbmNlIGluIGEgdmVjdG9yLgpgYGB7cn0KZGZfcGZpX3N2bSA8LSBhcy5kYXRhLmZyYW1lKHBmaV9zdm0pICU+JQogIGdyb3VwX2J5KHZhcmlhYmxlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9kcm9wb3V0X2xvc3MgPSBtZWFuKGRyb3BvdXRfbG9zcykpICU+JSBhcnJhbmdlKG1lYW5fZHJvcG91dF9sb3NzKSU+JSBzbGljZSgyOjYwKSAKc29ydGVkX3ZhcmlhYmxlcyA8LSBkZl9wZmlfc3ZtJHZhcmlhYmxlCmBgYAoKCkkgd2lsbCBzdGFydCB3aXRoIGluY2x1ZGluZyBvbmx5IHRoZSB0b3AgMTAgaW1wb3J0YW50IGZlYXR1cmVzIGZvciB0cmFpbmluZyBhbmQgcHJlZGljdGluZyB0aGUgY2FyZCB0eXBlcy4KVGhlbiBpbmNsdWRpbmcgYW5vdGhlciAzIGZlYXR1cmVzIGluIGVhY2ggc3RlcAoKYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgaW5pdGlhbGl6ZSB3aXRoIHRvcCAxMCBpbXBvcnRhbnQgZmVhdHVyZXMgYW5kIHRhcmdldCBjYXJkX3R5cGUKbiA9IDUgIyBpbmNsdWRlIG4gZmVhdHVyZXMgaW4gZWFjaCBzdGVwCnNlbGVjdGVkX2NvbHMgPC0gYygnY2FyZF90eXBlJywgc29ydGVkX3ZhcmlhYmxlc1sxOjEwXSkKY29sc19saXN0IDwtIGxpc3QoKQpzdm1fcmVzdWx0c190ZXN0XyA8LSBkYXRhLmZyYW1lKCkKc3ZtX21vZGVsc19saXN0XyA8LSBsaXN0KCkKc3ZtX3ByZWRpY3Rpb25zX3Rlc3RfbGlzdF8gPC0gbGlzdCgpCiMgUGVyZm9ybSBncmlkIHNlYXJjaCBmb3IgU1ZNCmZvciAoaSBpbiAxOmxlbmd0aChzb3J0ZWRfdmFyaWFibGVzKSkgewogIGlmIChsZW5ndGgoc2VsZWN0ZWRfY29scykgPD0gbGVuZ3RoKHNvcnRlZF92YXJpYWJsZXMpKzEpewogICAgY29sc19saXN0W1tpXV0gPC0gc2VsZWN0ZWRfY29scwogICAgZGZfdHJhaW5fc2VsZWN0ZWQgPC0gZGZfdHJhaW5bc2VsZWN0ZWRfY29sc10KICAgIGRmX3Rlc3Rfc2VsZWN0ZWQgPC0gZGZfdGVzdFtzZWxlY3RlZF9jb2xzXQogICAgc3ZtX21vZGVsXyA8LSBzdm0oCiAgICAgIGNhcmRfdHlwZSB+IC4sCiAgICAgIGRhdGEgPSBkZl90cmFpbl9zZWxlY3RlZCwKICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgd2VpZ2h0cyA9IGNsYXNzX3dlaWdodHMsCiAgICAgIHR5cGUgPSAiQy1jbGFzc2lmaWNhdGlvbiIsCiAgICAgIGtlcm5lbCA9ICJyYWRpYWwiLAogICAgICBjb3N0ID0gMTAuMCwKICAgICAgc2NhbGUgPSBUUlVFLCAgIyBTY2FsZSB0aGUgZGF0YSBmb3IgYmV0dGVyIHBlcmZvcm1hbmNlCiAgICAgIHByb2JhYmlsaXR5ID0gVFJVRSkKICAgIHN2bV9tb2RlbHNfbGlzdF9bW2ldXSA8LSBzdm1fbW9kZWxfCiAgICAjIEV2YWx1YXRlIG1vZGVsIG9uIHRlc3RzZXQKICAgIHRlc3RfcHJlZGljdGlvbnNfIDwtIGF0dHIocHJlZGljdChzdm1fbW9kZWxfLCBkZl90ZXN0X3NlbGVjdGVkLCBwcm9iYWJpbGl0eT1UUlVFKSwgInByb2JhYmlsaXRpZXMiKQogICAgdGVzdF9wcmVkaWN0aW9uc18gPC0gY2JpbmQodGVzdF9wcmVkaWN0aW9uc18sIGRmX3Rlc3RfY2xpZW50aWQpICU+JSBzZWxlY3QoYyhidXllcnMsIGNsaWVudF9pZCkpJT4lIG11dGF0ZShpZHggPSAxOm5yb3codGVzdF9wcmVkaWN0aW9uc18pLCBkZWNpc2lvbiA9IGFzLmZhY3RvcihpZmVsc2UoYnV5ZXJzID49IDAuNSwgJ2J1eWVycycsICdub25idXllcnMnKSkpIAogICAgc3ZtX3ByZWRpY3Rpb25zX3Rlc3RfbGlzdF9bW2ldXSA8LSB0ZXN0X3ByZWRpY3Rpb25zXwogICAgcmVzdWx0ID0gbWV0cmljc190YWJsZSh0ZXN0X3ByZWRpY3Rpb25zXyRkZWNpc2lvbiwgZGZfdGVzdF9zZWxlY3RlZCRjYXJkX3R5cGUsIGkpCiAgICBzdm1fcmVzdWx0c190ZXN0XyA8LSByYmluZChzdm1fcmVzdWx0c190ZXN0XywgcmVzdWx0KQogICAgIyBpbmNsdWRlIGFub3RoZXIgbiB2YXJpYWJsZXMKICAgIHN0YXJ0X2luZGV4ID0gbGVuZ3RoKHNlbGVjdGVkX2NvbHMpCiAgICBpZiAoc3RhcnRfaW5kZXggKyBuIC0gMSA8IGxlbmd0aChzb3J0ZWRfdmFyaWFibGVzKSl7CiAgICAgIGVuZF9pbmRleCA9IHN0YXJ0X2luZGV4ICsgbiAtIDEKICAgIH1lbHNlIHticmVha30KICAgIHNlbGVjdGVkX2NvbHMgPC0gYyhzZWxlY3RlZF9jb2xzLCBzb3J0ZWRfdmFyaWFibGVzW3N0YXJ0X2luZGV4OmVuZF9pbmRleF0pCiAgfWVsc2V7YnJlYWt9Cn0KCnN2bV9yZXN1bHRzX3Rlc3RfIDwtIG5hLm9taXQoc3ZtX3Jlc3VsdHNfdGVzdF8pICU+JQogIGFycmFuZ2UoZGVzYyhmMV9zY29yZSksIGRlc2MoQXVDKSwgZGVzYyhhY2N1cmFjeSksIGRlc2Moa29oZW5rYXBwYSkpCnN2bV9yZXN1bHRzX3Rlc3RfCmBgYAoKCmBgYHtyfQpzdm1fcmVzdWx0c190ZXN0XyRBdUMgPC0gYXMubnVtZXJpYyhzdm1fcmVzdWx0c190ZXN0XyRBdUMpCmRmX2xvbmcgPC0gcGl2b3RfbG9uZ2VyKHN2bV9yZXN1bHRzX3Rlc3RfLCBjb2xzID0gLW1vZGVsX2luZGV4LCBuYW1lc190byA9ICJ2YXJpYWJsZSIsIHZhbHVlc190byA9ICJ2YWx1ZSIpCgojIENyZWF0ZSB0aGUgbGluZSBwbG90CmdncGxvdChkZl9sb25nLCBhZXMoeCA9IG1vZGVsX2luZGV4LCB5ID0gdmFsdWUsIGNvbG9yID0gdmFyaWFibGUpKSArCiAgZ2VvbV9saW5lKCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh4ID0gIlN0ZXAgKDUgZmVhdHVyZXMgcGVyIHN0ZXApIiwgeSA9ICJtYXRyaWMgdmFsdWVzIiwgdGl0bGUgPSAiU3RlcHdpc2UgZmVhdHVyZSBzZWxlY3Rpb24iKQpgYGAKCkF0IHRoZSBzdGVwIDIsIG1vZGVsIGhhcyBpbmNyZWFzZWQgdG8gbGFyZ2VzdCBvZiBBdUMsIGYxLXNjb3JlLCBrb2hlbmthcHBhLCBhbmQgcmVjYWxsLiBBZnRlciB0aGlzIHN0ZXAsIHRoZSBtZXRyaWMgdmFsdWVzIGFyZSBkZWNyZWFzZWQuIFRoaXMgbWVhbnMsIHdpdGggdGhlIHNlbGVjdGVkIGZlYXR1cmVzIGF0IHN0ZXAgMiwgdGhlIFNWTSBtb2RlbCBwZXJmb3JtcyBiZXN0IG9uIHRlc3RzZXQ6IFdpdGggbGVzcyBudW1iZXIgb2YgZmVhdHVyZXMgKHN0ZXAgMSksIG1vZGVsIGlzIGxpa2VseSB1bmRlcmZpdHRpbmcuIFdpdGggbW9yZSBudW1iZXIgb2YgZmVhdHVyZXMgKHN0ZXAgMyB0aWxsIDEwKSwgbW9kZWwgaXMgbW9yZSBvdmVyZml0dGluZywgdGhlcmVmb3JlIGdlbmVyYWxpemUgd29yc2Ugb24gbmV3IGRhdGEuIAoKYGBge3J9CnNlbGVjdGVkX2ZlYXR1cmVzIDwtIGNvbHNfbGlzdFtbMl1dCnNlbGVjdGVkX2ZlYXR1cmVzCmBgYAoKYGBge3J9CnN2bV8yIDwtIHJiaW5kKHN2bV9yZXN1bHRzX3Rlc3RfWzEsXSxtZXRyaWNfcmVzdWx0c1sxLF0pCnN2bV8yJG1vZGVsX2luZGV4IDwtIGMoIjE2IHNlbGVjdGVkIGZlYXR1cmVzIiwiYWxsIGZlYXR1cmVzIikKc3ZtXzIKYGBgCgpgYGB7cn0KZXZhbF9sb25nIDwtIHN2bV8yICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gLW1vZGVsX2luZGV4LCBuYW1lc190byA9ICdtZXRyaWNzJywgdmFsdWVzX3RvID0gJ3ZhbHVlcycpCgpnZ3Bsb3QoZXZhbF9sb25nLCBhZXMoeCA9IGZhY3RvcihtZXRyaWNzKSwgeSA9IHZhbHVlcywgZ3JvdXAgPSBtb2RlbF9pbmRleCkpICsKZ2VvbV9saW5lKGFlcyhjb2xvciA9IG1vZGVsX2luZGV4KSkgKwpnZW9tX3BvaW50KGFlcyhjb2xvciA9IG1vZGVsX2luZGV4KSkgKwp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDEpKSArCnhsYWIoIiIpICsKZ2d0aXRsZSgiQ29tcGFyZSB0aGUgZWZmZWN0IG9mIGZlYXR1cmUgc2VsZWN0aW9uIikKYGBgCgpTdGVwd2lzZSBmZWF0dXJlIHNlbGVjdGlvbiBoYXMgbGFyZ2VseSByZWR1Y2VkIHRoZSBvdmVyZml0dGluZywgYW5kIGltcHJvdmVkIHRoZSBtb2RlbCBnZW5lcmFsaXphdGlvbi4gVGhlIG1vZGVsIHBlcmZvcm1hbmNlIG9uIHVuc2VlbiBkYXRhc2V0IGlzIGxhcmdlbHkgaW5jcmVhc2VkIGNvbXBhcmluZyB0byB0aGUgbW9kZWwgd2l0aCBhbGwgZmVhdHVyZXMuCgojIyMgNC4zIEZpbmFsIG1vZGVsOiBTVk0gd2l0aCAxNiBmZWF0dXJlcwoKIyMjIyA0LjMuMSBjb25mdXNpb24gbWF0cml4CgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KY29uZl9tYXRyaXggPC0gbWV0cmljc190YWJsZShzdm1fcHJlZGljdGlvbnNfdGVzdF9saXN0X1tbMl1dJGRlY2lzaW9uLCBkZl90ZXN0JGNhcmRfdHlwZSwgJ1NWTScsIHJldHVybl9jb25mX21hdHJpeD1UUlVFKQpnZ3Bsb3QoY29uZl9tYXRyaXgsIGFlcyhSZWZlcmVuY2UsIFByZWRpY3Rpb24sIGZpbGw9IEZyZXEpKSArCiAgICAgICAgZ2VvbV90aWxlKCkgKyBnZW9tX3RleHQoYWVzKGxhYmVsPUZyZXEpKSArCiAgICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IndoaXRlIiwgaGlnaD0iIzAwOTE5NCIpICsKICAgICAgICBsYWJzKHggPSAiUmVmZXJlbmNlIix5ID0gIlByZWRpY3Rpb24iLCB0aXRsZSA9ICJGaW5hbCBtb2RlbDogQ29uZnVzaW9uIG1hdHJpeCIpIApgYGAKLSAgIFRoZSBmaW5hbCBtb2RlbCBjb3VsZCBwcmVkaWN0IHZlcnkgd2VsbCBvZiBub25idXllcnMgZ3JvdXAgKDE5IG91dCBvZiA3NTAgbm9uYnV5ZXJzIGFyZSBmYWxzZSBwcmVkaWN0ZWQgYXMgYnV5ZXJzKSwgYnV0IHByZWRpY3QgaXMgc2xpZ2h0bHkgd29yc2Ugb24gdGhlIGJ1eWVycyBncm91cCAoNTcgb3V0IG9mIDE0OSB0cnVlIGJ1eWVycyBhcmUgZmFsc2VseSBwcmVkaWN0ZWQgYXMgbm9uYnV5ZXJzKS4KCiMjIyMgNC4zLjIgVG9wIGNsaWVudHMKCkZpbmFsbHkgdGhlIHRvcCA1JSBjbGllbnRzIHdpdGggdGhlIGhpZ2hlc3QgcG90ZW50aWFsOgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcHJlZGljdGlvbnNfdGVzdCA8LSBzdm1fcHJlZGljdGlvbnNfdGVzdF9saXN0X1tbMl1dICU+JSBhcnJhbmdlKGRlc2MoYnV5ZXJzKSkKdG9wXzVfY2xpZW50cyA8LXByZWRpY3Rpb25zX3Rlc3RbMTplbmRfaW5kZXhfNSxdICU+JSBsZWZ0X2pvaW4oZGZfdGVzdF9jbGllbnRpZCwgYnkgPSAiY2xpZW50X2lkIikgJT4lIHNlbGVjdChjKCJkZWNpc2lvbiIsImNhcmRfdHlwZSIpKQp0b3BfMTBfY2xpZW50cyA8LXByZWRpY3Rpb25zX3Rlc3RbMTplbmRfaW5kZXhfMTAsXSAlPiUgbGVmdF9qb2luKGRmX3Rlc3RfY2xpZW50aWQsIGJ5ID0gImNsaWVudF9pZCIpICU+JSBzZWxlY3QoYygiZGVjaXNpb24iLCJjYXJkX3R5cGUiKSkKCmNvbmZfbWF0cml4IDwtIG1ldHJpY3NfdGFibGUodG9wXzVfY2xpZW50cyRkZWNpc2lvbiwgdG9wXzVfY2xpZW50cyRjYXJkX3R5cGUsICdTVk0nLCByZXR1cm5fY29uZl9tYXRyaXg9VFJVRSkKcDEgPC0gZ2dwbG90KGNvbmZfbWF0cml4LCBhZXMoUmVmZXJlbmNlLCBQcmVkaWN0aW9uLCBmaWxsPSBGcmVxKSkgKwogICAgICAgIGdlb21fdGlsZSgpICsgZ2VvbV90ZXh0KGFlcyhsYWJlbD1GcmVxKSkgKwogICAgICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJ3aGl0ZSIsIGhpZ2g9IiMwMDkxOTQiKSArCiAgICAgICAgbGFicyh4ID0gIlJlZmVyZW5jZSIseSA9ICJQcmVkaWN0aW9uIiwgdGl0bGUgPSAiVG9wIDUlIGNsaWVudHM6IENvbmZ1c2lvbiBtYXRyaXgiKSAKY29uZl9tYXRyaXggPC0gbWV0cmljc190YWJsZSh0b3BfMTBfY2xpZW50cyRkZWNpc2lvbiwgdG9wXzEwX2NsaWVudHMkY2FyZF90eXBlLCAnU1ZNJywgcmV0dXJuX2NvbmZfbWF0cml4PVRSVUUpCnAyIDwtIGdncGxvdChjb25mX21hdHJpeCwgYWVzKFJlZmVyZW5jZSwgUHJlZGljdGlvbiwgZmlsbD0gRnJlcSkpICsKICAgICAgICBnZW9tX3RpbGUoKSArIGdlb21fdGV4dChhZXMobGFiZWw9RnJlcSkpICsKICAgICAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSIjMDA5MTk0IikgKwogICAgICAgIGxhYnMoeCA9ICJSZWZlcmVuY2UiLHkgPSAiUHJlZGljdGlvbiIsIHRpdGxlID0gIlRvcCAxMCUgY2xpZW50czogQ29uZnVzaW9uIG1hdHJpeCIpIApncmlkLmFycmFuZ2UocDEscDIsbnJvdz0xLG5jb2w9MikKYGBgCi0gICBUaGUgdG9wIDUlIGNsaWVudHMgbGlzdCAoNDQgY2xpZW50cykgaW5jbHVkZWQgb25seSAxIHRydWUgbm9uYnV5ZXJzLgotICAgVGhlIHRvcCAxMCUgY2xpZW50cyBsaXN0ICg4OSBjbGllbnRzKSBpbmNsdWRlZCA3IHRydWUgbm9uYnV5ZXJzLCB0aGlzIGlzIHdvcnNlIHRoYW4gdGhlIHRvcCA1JSBjbGllbnRzIHByZWRpY3Rpb24uCi0gICBUaGUgZmluYWwgbW9kZWwgY291bGQgcHJlZGljdCB0aGUgcG90ZW50aWFsIGJ1eWVycyB2ZXJ5IGdvb2QuIFByZWRpY3Rpb24gd29ya3MgZXNwZWNpYWxseSBnb29kIG9uIHRoZSBtb3N0IHRvcCBjbGllbnRzIChlLmcuIDUlLCBvciBldmVuIGxlc3MpLCB3aGVuIHJlcXVpcmluZyBsYXJnZXIgbnVtYmVyIG9mIHBvdGVudGlhbCBidXllcnMsIHRoZSBwZXJmb3JtYW5jZSB3aWxsIHNsaWdodGx5IGRlY3JlYXNlLiAKCgpCcmVhayBkb3duIHByb2ZpbGUgb2YgdGhlIHRvcCAxMCBjbGllbnRzIHRvIHVuZGVyc3RhbmQgdGhlIGZlYXR1cmUgY29udHJpYnV0aW9ucyBpbiBlYWNoIGNhc2UuCgpgYGB7cn0KZmluYWxfbW9kZWwgPC0gc3ZtX21vZGVsc19saXN0X1tbMl1dCmRmX3RyYWluX2ZpbmFsIDwtIGRmX3RyYWluW3NlbGVjdGVkX2ZlYXR1cmVzXQpkZl90ZXN0X2ZpbmFsIDwtIGRmX3Rlc3Rbc2VsZWN0ZWRfZmVhdHVyZXNdCiMgZXhwbGFpbmVyCmV4cGxhaW5lcl9maW5hbCA8LSBleHBsYWluKGZpbmFsX21vZGVsLCBkYXRhID0gZGZfdHJhaW5fZmluYWwsIHk9YXMubnVtZXJpYyhkZl90cmFpbl9maW5hbCRjYXJkX3R5cGUpLCBsYWJlbD0nc3ZtIG1vZGVsJykKYGBgCmBgYHtyfQpuID0gMTAKcGxvdF9saXN0IDwtIGxpc3QoKQpmb3IgKGkgaW4gc2VxKDEsIG4pKSB7CiAgaWR4ID0gcHJlZGljdGlvbnNfdGVzdFtpLDNdCiAgY2xpZW50X2lkID0gcHJlZGljdGlvbnNfdGVzdFtpLDJdCiAgYmRwX2ZpbmFsIDwtIHByZWRpY3RfcGFydHMoZXhwbGFpbmVyX2ZpbmFsLCBuZXdfb2JzZXJ2YXRpb24gPSBkZl90ZXN0X2ZpbmFsW2lkeCwgXSkKICBwIDwtIHBsb3QoYmRwX2ZpbmFsKSArIGdndGl0bGUocGFzdGUoIkJyZWFrIGRvd24gcHJvZmlsZSAtIENsaWVudF9pZCAiLGNsaWVudF9pZCkpCiAgcGxvdF9saXN0W1tpXV0gPC0gcH0KCmZvciAoaSBpbiAxOm4pIHsKICBwcmludChwbG90X2xpc3RbW2ldXSkKfQpgYGAKCi0gICBJbiB0aGUgdG9wIDEwIGNsaWVudHMsIHRoZSBtYWluIGNvbnRyaWJ1dGVkIGZlYXR1cmUgaXMgZWl0aGVyIHNhZF90cmFucywgbWF4X3RyYW5zLCBvciBzbG9wZV90cmFucy4KCiMjIyMgNC4zLjMgZmVhdHVyZXMgcGVybXV0YXRpb24gaW1wb3J0YW5jZQoKYGBge3J9CnNldC5zZWVkKDEyMykKcGZpX2ZpbmFsIDwtIG1vZGVsX3BhcnRzKGV4cGxhaW5lcl9maW5hbCwgdHlwZSA9ICJ2YXJpYWJsZV9pbXBvcnRhbmNlIiwgTiA9IDEwMDApCnBsb3QocGZpX2ZpbmFsLCBzaG93X2JveHBsb3RzID0gRkFMU0UpICsgZ2d0aXRsZSgiRmluYWwgU1ZNIG1vZGVsOiBwZXJmdW1hdGlvbiBpbXBvcnRhbmNlIikKYGBgCgotICAgU2ltaWxhcmx5IGFzIHByZXZpb3VzbHkgKHRoZSBTVk0gbW9kZWwgd2l0aCBmdWxsIGZlYXR1cmVzKSBzaG93biwgbWF4X3RyYW5zIGFwcGVhcmVkIHRvIGJlIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlLCB3aXRoIGEgc2lnbmlmaWNhbnRseSBsYXJnZXIgaW1wb3J0YW5jZSB0aGFuIG90aGVycy4gVGhpcyBtZWFucywgaWYgdGhlIHZhbHVlcyBvZiBtYXhfdHJhbnMgYXJlIG1peGVkIHVwLCB0aGUgbW9kZWwgcGVyZm9ybWFuY2Ugd2lsbCBiZSBsYXJnZWx5IHJlZHVjZWQuCgoKIyMgNSBEZXNjcmliZSByZXN1bHQgb2YgdGhlIOKAnGZpbmFs4oCdIG1vZGVsIGZvciB0aGUgYmFuayBzdGFmZgogCkFmdGVyIGFuYWx5emluZyB0aGUgMTItbW9udGggcm9sbHVwIGluZm9ybWF0aW9uLCBteSBmaW5hbCBtb2RlbCBmb3VuZCB0aGUgZm9sbG93aW5nIGZhY3RvcnMgYXMgdGhlIG1vc3QgaW1wb3J0YW50IG9uZXMgZm9yIGlkZW50aWZ5aW5nIHBvdGVudGlhbCBjcmVkaXQgY2FyZCBidXllcnMuIEl0IHdpbGwgYmUgaGVscGZ1bCBmb3IgdGhlIGJhbmsgc3RhZmYgdG8gZWZmaWNpZW50bHkgc2VsbGluZyBjcmVkaXQgY2FyZHMgYXMgd2VsbCBhcyBvdGhlciBiYW5rIHByb2R1Y3RzLgoKKip0cmFuc2FjdGlvbioqIHZhbHVlcyBhbmQgZGlzdHJpYnV0aW9uOiAKICAtICAgbWF4aW1hbCB0cmFuc2FjdGlvbjogbGFyZ2VzdCBzaW5nbGUgdHJhbnNhY3Rpb24gYSBjbGllbnQgaGFzIG1hZGUuIEEgaGlnaCBtYXhpbXVtIHRyYW5zYWN0aW9uIG1pZ2h0IHN1Z2dlc3QgYSBjdXN0b21lciB3aG8gbWFrZXMgbGFyZ2UgaW52ZXN0bWVudHMgb3Igc2lnbmlmaWNhbnQgcHVyY2hhc2VzIGFuZCBjb3VsZCBiZSBpbnRlcmVzdGVkIGluIGNyZWRpdCBjYXJkIG9yIG90aGVyIHByb2R1Y3RzIGZvciBoaWdoLXZhbHVlIHRyYW5zYWN0aW9ucy4KICAtICAgc3VtIG9mIGFic29sdXRlIGRpZmZlcmVuY2U6IGl0IHJlZmxlY3RzIGhvdyBtdWNoIGEgY2xpZW50J3MgdHJhbnNhY3Rpb25zIGdvIHVwIGFuZCBkb3duLiBJZiB0aGlzIG51bWJlciBpcyBoaWdoLCBpdCBtZWFucyB0aGUgY3VzdG9tZXIncyB0cmFuc2FjdGlvbiBhbW91bnRzIGNoYW5nZSBhIGxvdC4gVGhpcyBjb3VsZCBpbmRpY2F0ZSBzb21lb25lIHdobyBoYXMgaXJyZWd1bGFyIGluY29tZSBvciBleHBlbnNlcyBhbmQgbWlnaHQgbmVlZCBmbGV4aWJsZSBiYW5raW5nIHNvbHV0aW9ucy4KICAtICAgc2xvcGU6IGl0IHRlbGxzIGlmIGEgY2xpZW50J3MgdHJhbnNhY3Rpb25zIGFyZSBnZW5lcmFsbHkgaW5jcmVhc2luZyBvciBkZWNyZWFzaW5nIG92ZXIgdGltZS4gQW4gaW5jcmVhc2luZyBzbG9wZSBjb3VsZCBtZWFuIHRoZSBjdXN0b21lcidzIGZpbmFuY2lhbCBhY3Rpdml0eSBpcyBncm93aW5nLCBwb3RlbnRpYWxseSBhIGdvb2QgY2FuZGlkYXRlIGZvciBzYXZpbmdzIGFuZCBpbnZlc3RtZW50IHByb2R1Y3RzLgogIC0gICB1cHBlciBxdWFydGlsZTogaXQgZ2l2ZXMgYW4gaWRlYSBvZiB0aGUgY2xpZW50J3MgdXBwZXItcmFuZ2UgdHJhbnNhY3Rpb24gYW1vdW50LiBDdXN0b21lcnMgd2l0aCBhIGhpZ2ggdXBwZXIgcXVhcnRpbGUgbWlnaHQgYmUgbW9yZSBsaWtlbHkgdG8gYnV5IHByZW1pdW0gYmFua2luZyBwcm9kdWN0cwogIC0gICBtZWRpYW4gYWJzb2x1dGUgZGV2aWF0aW9uOiBpdCBtZWFzdXJlcyBob3cgdmFyaWVkIGEgY3VzdG9tZXIncyB0cmFuc2FjdGlvbnMgYXJlLiBBIGhpZ2ggdmFsdWUgbWF5IGluZGljYXRlIGEgZmluYW5jaWFsIGxpZmUgd2l0aCBhIGxvdCBvZiB1cHMgYW5kIGRvd25zLgogIC0gICBudW1iZXIgb2YgdHJhbnNhY3Rpb25zIGFyZSBhYm92ZSB0aGUgbWVhbjogSWYgYSBjbGllbnQgb2Z0ZW4gaGFzIHRyYW5zYWN0aW9ucyBhYm92ZSB0aGVpciBhdmVyYWdlLCB0aGV5IG1pZ2h0IGJlIGV4cGVyaWVuY2luZyBncm93dGggb3IgaGF2ZSB2YXJpYWJsZSBpbmNvbWUuCiAgLSAgIG1lZGlhbjogaXQncyBhIGdvb2QgaW5kaWNhdG9yIG9mIHdoYXQgYSB0eXBpY2FsIHRyYW5zYWN0aW9uIGxvb2tzIGxpa2UsIHdoaWNoIGNhbiBoZWxwIGluIHVuZGVyc3RhbmRpbmcgYSBjbGllbnQncyB1c3VhbCBmaW5hbmNpYWwgYmVoYXZpb3IuCiAgLSAgIHZhcmlhbmNlOiBpdCBtZWFzdXJlcyBob3cgbXVjaCBhIGN1c3RvbWVyJ3MgdHJhbnNhY3Rpb25zIGRpZmZlciBmcm9tIGVhY2ggb3RoZXIuIEEgaGlnaCB2YXJpYW5jZSBtZWFucyBhIGxvdCBvZiB2YXJpYWJpbGl0eSwgcG9zc2libHkgaW5kaWNhdGluZyBhIGN1c3RvbWVyIHdpdGggdW5wcmVkaWN0YWJsZSBmaW5hbmNpYWwgbmVlZHMuCiAgCioqYXNzZXQ6KioKICAtICAgc3VtIG9mIGFic29sdXRlIGRpZmZlcmVuY2U6IHNpbWlsYXIgdG8gdHJhbnNhY3Rpb25zLCBhIGhpZ2ggU0FEIG9mIGFzc2V0cyBzdWdnZXN0cyB0aGUgY2xpZW50J3MgYWNjb3VudCBiYWxhbmNlIGNoYW5nZXMgZnJlcXVlbnRseSwgaW5kaWNhdGluZyBhIGR5bmFtaWMgZmluYW5jaWFsIHNpdHVhdGlvbi4KICAtICAgcm9idXN0IHRyZW5kOiBpdCBpbmRpY2F0ZXMgd2hldGhlciB0aGUgY2xpZW50J3MgYXNzZXRzIGFyZSBjb25zaXN0ZW50bHkgaW5jcmVhc2luZywgZGVjcmVhc2luZywgb3Igc3RheWluZyBhYm91dCB0aGUgc2FtZSBvdmVyIHRpbWUuIEEgcG9zaXRpdmUgdHJlbmQgaW5kaWNhdGVzIHNpZ25hbCBmaW5hbmNpYWwgZ3Jvd3RoLCB0aGUgY2xpZW50IG1pZ2h0IGJlIG9wZW4gdG8gaW52ZXN0bWVudCBvcHBvcnR1bml0aWVzLgogIC0gICBudW1iZXIgb2YgYXNzZXQgYXJlIHBvc2l0aXZlOiBNb3JlIHBvc2l0aXZlIHJlY29yZHMgbWlnaHQgaW5kaWNhdGUgYSBzdGFibGUgb3IgZ3Jvd2luZyBmaW5hbmNpYWwgc2l0dWF0aW9uLCBzdWdnZXN0aW5nIHRoZXkgbWlnaHQgYmUgZ29vZCBjYW5kaWRhdGVzIGZvciBhZGRpdGlvbmFsIGZpbmFuY2lhbCBwcm9kdWN0cy4KICAtICAgbWluaW1hbCB2YWx1ZTogaXQgaXMgdGhlIHNtYWxsZXN0IGFtb3VudCB0aGUgY2xpZW50IGhhcyBoYWQgaW4gdGhlaXIgYWNjb3VudC4gSXQgY2FuIGhlbHAgdW5kZXJzdGFuZCB0aGUgY3VzdG9tZXIncyBmaW5hbmNpYWwgbG93cyBhbmQgcG9zc2libHkgb2ZmZXIgcHJvZHVjdHMgdGhhdCBoZWxwIG9mIGxvd2VyIGJhbGFuY2VzLgogIC0gICBzdGFuZGFyZCBkZXZpYXRpb246IGl0IHRlbGxzIGhvdyBtdWNoIGEgY2xpZW50J3MgYWNjb3VudCBiYWxhbmNlIHZhcmllcy4gQSBjbGllbnQgd2l0aCBhIGhpZ2ggc3RhbmRhcmQgZGV2aWF0aW9uIG1heSBuZWVkIHNlcnZpY2VzIGluIG1hbmFnaW5nIGZpbmFuY2lhbCByaXNrLgogIC0gICBudW1iZXIgb2YgYXNzZXQgYXJlIGFib3ZlIHRoZSBtZWFuOiBob3cgb2Z0ZW4gYSBjdXN0b21lcidzIGFjY291bnQgYmFsYW5jZSBpcyBhYm92ZSB0aGVpciBhdmVyYWdlIGJhbGFuY2UuIEEgbGFyZ2UgbnVtYmVyIGluZGljYXRlcywgdGhlIGNsaWVudCBtaWdodCBiZSBhIGdvb2QgcHJvc3BlY3QgZm9yIHNhdmluZ3Mgb3IgaW52ZXN0bWVudCBwcm9kdWN0cyBkdWUgdG8gdGhlaXIgaGFiaXQgb2YgbWFpbnRhaW5pbmcgaGlnaGVyIGJhbGFuY2VzLgogIAoqKmFjY291bnRfYWdlOioqIGhvdyBsb25nIHRoZSBjbGllbnQgaGFzIGhhZCB0aGlzIGFjY291bnQgd2l0aCB0aGUgYmFuay4gQSBsb25nZXIgYWNjb3VudCBhZ2UgY291bGQgaW5kaWNhdGUgbG95YWx0eSBhbmQgZmFtaWxpYXJpdHkgd2l0aCB0aGUgYmFuaydzIHNlcnZpY2VzLCBhbmQgdGhlc2UgY2xpZW50cyBtaWdodCBiZSBtb3JlIHJlY2VwdGl2ZSB0byBuZXcgb2ZmZXJzLgpGb3IgdGhlIGJhbmsgc3RhZmYsIHRoZXNlIGZlYXR1cmVzIHByb3ZpZGUgY2x1ZXMgYWJvdXQgYSBjdXN0b21lcidzIGZpbmFuY2lhbCBoZWFsdGggYW5kIGJlaGF2aW9yLiBVbmRlcnN0YW5kaW5nIHRoZXNlIGNhbiBoZWxwIGluIHN1Z2dlc3RpbmcgdGhlIHJpZ2h0IHByb2R1Y3RzIHRvIHRoZSBjdXN0b21lcnMsIHN1Y2ggYXMgc2F2aW5ncyBhY2NvdW50cywgaW52ZXN0bWVudCBvcHBvcnR1bml0aWVzLCBmaW5hbmNpYWwgcGxhbm5pbmcgc2VydmljZXMsIG9yIGV2ZW4gc3BlY2lhbCBwcm9ncmFtcyBmb3IgdGhvc2Ugd2l0aCBoaWdoIHRyYW5zYWN0aW9uIHZvbHVtZXMuIAoK